diff --git a/wire/core/ImageSizerEngine.php b/wire/core/ImageSizerEngine.php
index d2a670dc..de09c4dc 100755
--- a/wire/core/ImageSizerEngine.php
+++ b/wire/core/ImageSizerEngine.php
@@ -3,15 +3,13 @@
/**
* ImageSizer Engine Module (Abstract)
*
- * Copyright (C) 2016 by Horst Nogajski and Ryan Cramer
+ * Copyright (C) 2016-2019 by Horst Nogajski and Ryan Cramer
* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
*
* @property bool $autoRotation
* @property bool $upscaling
* @property bool $interlace
* @property array|string|bool $cropping
- * @property bool $webpAdd
- * @property int $webpQuality
* @property int $quality
* @property string $sharpening
* @property float $defaultGamma
@@ -20,6 +18,9 @@
* @property string $flip
* @property bool $useUSM
* @property int $enginePriority Priority for use among other ImageSizerEngine modules (0=disabled, 1=first, 2=second, 3=and so on)
+ * @property bool $webpAdd
+ * @property int $webpQuality
+ * @property bool|null $webpResult
*
*/
abstract class ImageSizerEngine extends WireData implements Module, ConfigurableModule {
@@ -78,6 +79,14 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*/
protected $webpAdd = false;
+ /**
+ * webp result (null=not known or not applicable)
+ *
+ * @var bool|null
+ *
+ */
+ protected $webpResult = null;
+
/**
* Image interlace setting, false or true
*
@@ -902,10 +911,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
*/
public function setQuality($n) {
- $n = (int) $n;
- if($n < 1) $n = 1;
- else if($n > 100) $n = 100;
- $this->quality = $n;
+ $this->quality = $this->getIntegerValue($n, 1, 100);
return $this;
}
@@ -918,10 +924,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
*
*/
public function setWebpQuality($n) {
- $n = (int) $n;
- if($n < 1) $n = 1;
- else if($n > 100) $n = 100;
- $this->webpQuality = $n;
+ $this->webpQuality = $this->getIntegerValue($n, 1, 100);
return $this;
}
@@ -1252,6 +1255,25 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
if(in_array(strtolower($value), array('0', 'off', 'false', 'no', 'n', 'none'))) return false;
return ((int) $value) > 0;
}
+
+ /**
+ * Get integer value within given range
+ *
+ * @param int $n Number to require in given range
+ * @param int $min Minimum allowed number
+ * @param int $max Maximum allowed number
+ * @return int
+ *
+ */
+ protected function getIntegerValue($n, $min, $max) {
+ $n = (int) $n;
+ if($n < $min) {
+ $n = $min;
+ } else if($n > $max) {
+ $n = $max;
+ }
+ return $n;
+ }
/**
* Return an array of the current options
@@ -1305,6 +1327,7 @@ abstract class ImageSizerEngine extends WireData implements Module, Configurable
'options'
);
+ if($key === 'webpResult') return $this->webpResult;
if(in_array($key, $keys)) return $this->$key;
if(in_array($key, $this->optionNames)) return $this->$key;
if(isset($this->options[$key])) return $this->options[$key];
diff --git a/wire/core/ImageSizerEngineGD.php b/wire/core/ImageSizerEngineGD.php
index 97bad910..a2c97425 100755
--- a/wire/core/ImageSizerEngineGD.php
+++ b/wire/core/ImageSizerEngineGD.php
@@ -8,10 +8,12 @@
*
* Other user contributions as noted.
*
- * Copyright (C) 2016 by Horst Nogajski and Ryan Cramer
+ * Copyright (C) 2016-2019 by Horst Nogajski and Ryan Cramer
* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/
*
* https://processwire.com
+ *
+ * @method bool imSaveReady($im, $filename)
*
*/
class ImageSizerEngineGD extends ImageSizerEngine {
@@ -34,6 +36,22 @@ class ImageSizerEngineGD extends ImageSizerEngine {
*/
protected $gammaLinearized;
+ /**
+ * webp-only toggle for future use
+ *
+ * @var bool
+ *
+ */
+ protected $webpOnly = false;
+
+ /**
+ * Webp support available?
+ *
+ * @var bool|null
+ *
+ */
+ static protected $webpSupport = null;
+
/**
* Get formats GD and resize
*
@@ -72,12 +90,12 @@ class ImageSizerEngineGD extends ImageSizerEngine {
break;
case 'webp':
- if(!isset($this->wire('config')->webpSupportGD)) {
+ if(self::$webpSupport === null) {
// only call it once
$gd = gd_info();
- $this->wire('config')->webpSupportGD = isset($gd['WebP Support']) ? $gd['WebP Support'] : false;
+ self::$webpSupport = isset($gd['WebP Support']) ? $gd['WebP Support'] : false;
}
- return $this->wire('config')->webpSupportGD;
+ return self::$webpSupport;
break;
case 'install':
@@ -335,61 +353,29 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// write to file(s)
if(file_exists($dstFilename)) $this->wire('files')->unlink($dstFilename);
- $result = false;
+
+ $result = null; // null=not yet known
+
switch($this->imageType) {
case \IMAGETYPE_GIF:
// correct gamma from linearized 1.0 back to 2.0
$this->gammaCorrection($thumb, false);
-
- // If only a WebP file is required
- if($this->webpOnly) {
- $result = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
-
- } else {
- // optionally save an additional WebP file
- if($this->webpAdd) {
- $resultWebp = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
- }
-
- // save the final GIF image file
- $result = imagegif($thumb, $dstFilename);
- }
+ // save the final GIF image file
+ if($this->imSaveReady($thumb, $srcFilename)) $result = imagegif($thumb, $dstFilename);
break;
case \IMAGETYPE_PNG:
// optionally correct gamma from linearized 1.0 back to 2.0
if(!$this->hasAlphaChannel()) $this->gammaCorrection($thumb, false);
-
- // If only a WebP file is required
- if($this->webpOnly) {
- $result = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
-
- } else {
- // optionally save an additional WebP file
- if($this->webpAdd) {
- $resultWebp = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
- }
-
- // save the final PNG image file and always use highest compression level (9) per @horst
- $result = imagepng($thumb, $dstFilename, 9);
- }
+ // save the final PNG image file and always use highest compression level (9) per @horst
+ if($this->imSaveReady($thumb, $srcFilename)) $result = imagepng($thumb, $dstFilename, 9);
break;
case \IMAGETYPE_JPEG:
// correct gamma from linearized 1.0 back to 2.0
$this->gammaCorrection($thumb, false);
-
- // If only a WebP file is required
- if($this->webpOnly) {
- $result = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
-
- } else {
- // optionally save an additional WebP file
- if($this->webpAdd) {
- $resultWebp = $this->imSaveWebP($thumb, $srcFilename, $this->webpQuality);
- }
-
+ if($this->imSaveReady($thumb, $srcFilename)) {
// optionally apply interlace bit to the final image. this will result in progressive JPEGs
if($this->interlace) {
if(0 == imageinterlace($thumb, 1)) {
@@ -397,26 +383,46 @@ class ImageSizerEngineGD extends ImageSizerEngine {
// ...
}
}
-
// save the final JPEG image file
$result = imagejpeg($thumb, $dstFilename, $this->quality);
}
break;
+
+ default:
+ $result = false;
}
-
+
// release the last GD image object
if(isset($thumb) && is_resource($thumb)) @imagedestroy($thumb);
if(isset($thumb)) $thumb = null;
+ if($result === null) $result = $this->webpResult; // if webpOnly option used
return $result;
}
+
+ /**
+ * Called before saving of image, returns true if save should proceed, false if not
+ *
+ * Also Creates a webp file when settings indicate it should.
+ *
+ * @param resource $im
+ * @param string $filename Source filename
+ * @return bool
+ *
+ */
+ protected function ___imSaveReady($im, $filename) {
+ if($this->webpOnly || $this->webpAdd) {
+ $this->webpResult = $this->imSaveWebP($im, $filename, $this->webpQuality);
+ }
+ return $this->webpOnly ? false : true;
+ }
/**
* Create WebP image (@horst)
* Is requested by image options: ["webpAdd" => true] OR ["webpOnly" => true]
*
* @param resource $im
- * @param string $dstFilename
+ * @param string $filename
* @param int $quality
*
* @return boolean true | false
@@ -582,7 +588,7 @@ class ImageSizerEngineGD extends ImageSizerEngine {
* with mode = true it linearizes an image to 1
* with mode = false it set it back to the originating gamma value
*
- * @param GD -image-resource $image
+ * @param resource $image
* @param bool $mode
*
*/
@@ -817,8 +823,8 @@ class ImageSizerEngineGD extends ImageSizerEngine {
*
* Intended for use by the resize() method
*
- * @param GD -resource $im, destination resource needs to be prepared
- * @param GD -resource $image, with GIF we need to read from source resource
+ * @param resource $im, destination resource needs to be prepared
+ * @param resource $image, with GIF we need to read from source resource
*
*/
protected function prepareImageLayer(&$im, &$image) {
diff --git a/wire/core/Pagefile.php b/wire/core/Pagefile.php
index 594912ce..7f1c2392 100644
--- a/wire/core/Pagefile.php
+++ b/wire/core/Pagefile.php
@@ -63,6 +63,12 @@ class Pagefile extends WireData {
*/
protected $pagefiles;
+ /**
+ * @var PagefileExtra[]
+ *
+ */
+ protected $extras = array();
+
/**
* Extra file data
*
@@ -952,8 +958,13 @@ class Pagefile extends WireData {
*
*/
public function unlink() {
- if(!strlen($this->basename) || !is_file($this->filename)) return true;
- return $this->wire('files')->unlink($this->filename, true);
+ /** @var WireFileTools $files */
+ if(!strlen($this->basename) || !is_file($this->filename)) return true;
+ $files = $this->wire('files');
+ foreach($this->extras() as $extra) {
+ $extra->unlink();
+ }
+ return $files->unlink($this->filename, true);
}
/**
@@ -968,10 +979,17 @@ class Pagefile extends WireData {
*
*/
public function rename($basename) {
+ foreach($this->extras() as $extra) {
+ $extra->filename(); // init
+ }
$basename = $this->pagefiles->cleanBasename($basename, true);
if($this->wire('files')->rename($this->filename, $this->pagefiles->path . $basename, true)) {
$this->set('basename', $basename);
- return $this->basename();
+ $basename = $this->basename();
+ foreach($this->extras() as $extra) {
+ $extra->rename();
+ }
+ return $basename;
}
return false;
}
@@ -986,8 +1004,13 @@ class Pagefile extends WireData {
*
*/
public function copyToPath($path) {
- $result = copy($this->filename, $path . $this->basename());
- if($this->config->chmodFile) chmod($path . $this->basename(), octdec($this->config->chmodFile));
+ /** @var WireFileTools $files */
+ $files = $this->wire('files');
+ $result = $files->copy($this->filename(), $path);
+ foreach($this->extras() as $extra) {
+ if(!$extra->exists()) continue;
+ $files->copy($extra->filename, $path);
+ }
return $result;
}
@@ -1040,6 +1063,25 @@ class Pagefile extends WireData {
return $this->pagefiles->isTemp($this, $set);
}
+ /**
+ * Get all extras, add an extra, or get an extra
+ *
+ * #pw-internal
+ *
+ * @param string $name
+ * @param PagefileExtra $value
+ * @return PagefileExtra[]|PagefileExtra|null
+ * @since 3.0.132
+ *
+ */
+ public function extras($name = null, PagefileExtra $value = null) {
+ if($name === null) return $this->extras;
+ if($value !== null && $value instanceof PagefileExtra) {
+ $this->extras[$name] = $value;
+ }
+ return isset($this->extras[$name]) ? $this->extras[$name] : null;
+ }
+
/**
* Debug info
*
diff --git a/wire/core/PagefileExtra.php b/wire/core/PagefileExtra.php
new file mode 100644
index 00000000..b319c992
--- /dev/null
+++ b/wire/core/PagefileExtra.php
@@ -0,0 +1,228 @@
+wire($this);
+ $this->setPagefile($pagefile);
+ $this->setExtension($extension);
+ return parent::__construct();
+ }
+
+ /**
+ * Set Pagefile instance this extra is connected to
+ *
+ * @param Pagefile $pagefile
+ *
+ */
+ public function setPagefile(Pagefile $pagefile) {
+ $this->pagefile = $pagefile;
+ }
+
+ /**
+ * Set extension for this extra
+ *
+ * @param $extension
+ *
+ */
+ public function setExtension($extension) {
+ $this->extension = $extension;
+ }
+
+ /**
+ * Does the extra file currently exist?
+ *
+ * @return bool
+ *
+ */
+ public function exists() {
+ return is_readable($this->filename());
+ }
+
+ /**
+ * Return the file size in bytes
+ *
+ * @return int
+ *
+ */
+ public function filesize() {
+ return $this->exists() ? filesize($this->filename()) : 0;
+ }
+
+ /**
+ * Return the full server disk path to the extra file, whether it exists or not
+ *
+ * @return string
+ *
+ */
+ public function filename() {
+ $pathinfo = pathinfo($this->pagefile->filename());
+ $filename = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '.' . $this->extension;
+ if(empty($this->filenamePrevious)) $this->filenamePrevious = $filename;
+ return $filename;
+ }
+
+ /**
+ * Return just the basename (no path)
+ *
+ * @return string
+ *
+ */
+ public function basename() {
+ return basename($this->filename());
+ }
+
+ /**
+ * Return the URL to the extra file, creating it if it does not already exist
+ *
+ * @return string
+ *
+ */
+ public function url() {
+ if(!$this->exists()) $this->create();
+ $pathinfo = pathinfo($this->pagefile->url());
+ return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '.' . $this->extension;
+ }
+
+ /**
+ * Return the HTTP URL to the extra file
+ *
+ * @return string
+ *
+ */
+ public function httpUrl() {
+ return str_replace($this->pagefile->url(), $this->url(), $this->pagefile->httpUrl());
+ }
+
+ /**
+ * Unlink/delete the extra file
+ *
+ * @return bool
+ *
+ */
+ public function unlink() {
+ if(!$this->exists()) return false;
+ return $this->wire('files')->unlink($this->filename());
+ }
+
+ /**
+ * Rename the extra file to be consistent with Pagefile name
+ *
+ * @return bool
+ *
+ */
+ public function rename() {
+ if(!$this->exists()) return false;
+ if(!$this->filenamePrevious) return false;
+ return $this->wire('files')->rename($this->filenamePrevious, $this->filename());
+ }
+
+ /**
+ * Create the extra file
+ *
+ * Must be implemented by a hook or by descending class
+ *
+ */
+ public function ___create() { }
+
+ /**
+ * Get property
+ *
+ * @param string $key
+ * @return bool|int|mixed|null|string
+ *
+ */
+ public function get($key) {
+ switch($key) {
+ case 'exists':
+ $value = $this->exists();
+ break;
+ case 'filesize':
+ $value = $this->filesize();
+ break;
+ case 'url':
+ $value = $this->url();
+ break;
+ case 'filename':
+ case 'pathname':
+ $value = $this->filename();
+ break;
+ case 'filenamePrevious':
+ $value = $this->filenamePrevious && $this->filenamePrevious != $this->filename() ? $this->filenamePrevious : '';
+ break;
+ case 'basename':
+ $value = $this->basename();
+ break;
+ case 'ext':
+ case 'extension':
+ $value = $this->extension;
+ break;
+ case 'URL':
+ case 'HTTPURL':
+ $value = str_replace($this->pagefile->url(), $this->url(), $this->pagefile->$key);
+ break;
+ case 'pagefile':
+ $value = $this->pagefile;
+ break;
+ default:
+ $value = $this->pagefile->get($key);
+ }
+ return $value;
+ }
+
+ /**
+ * @return string
+ *
+ */
+
+ public function __toString() {
+ return $this->basename();
+ }
+}
\ No newline at end of file
diff --git a/wire/core/Pageimage.php b/wire/core/Pageimage.php
index 82baba46..1cf88ad6 100644
--- a/wire/core/Pageimage.php
+++ b/wire/core/Pageimage.php
@@ -40,11 +40,7 @@
* @property-read string $suffixStr String of file suffix(es) separated by comma.
* @property-read string $alt Convenient alias for the 'description' property, unless overridden (since 3.0.125).
* @property-read string $src Convenient alias for the 'url' property, unless overridden (since 3.0.125).
- * @property-read string $urlWebp The url property of an optional WebP-dependency file (since 3.0.132).
- * @property-read string $srcWebp Convenient alias for the 'urlWebp' property (since 3.0.132).
- * @property-read string $webpUrl Convenient alias for the 'urlWebp' property (since 3.0.132).
- * @property-read string $webpSrc Convenient alias for the 'urlWebp' property (since 3.0.132).
- * @property-read bool $hasWebp Does exist an optional WebP-dependency file for this image variation? (since 3.0.132)
+ * @property-read PagefileExtra $webp Access webp version of image (since 3.0.132)
*
* Properties inherited from Pagefile
* ==================================
@@ -72,6 +68,7 @@
* @property Pagefiles $pagefiles The Pagefiles WireArray that contains this file. #pw-group-other
* @property Page $page The Page object that this file is part of. #pw-group-other
* @property Field $field The Field object that this file is part of. #pw-group-other
+ * @property PageimageDebugInfo $debugInfo
*
* Hookable methods
* ================
@@ -122,7 +119,13 @@ class Pageimage extends Pagefile {
private $imageInfo = array(
'width' => 0,
'height' => 0,
- );
+ );
+
+ /**
+ * @var PageimageDebugInfo|null
+ *
+ */
+ private $pageimageDebugInfo = null;
/**
* Last size error, if one occurred.
@@ -132,6 +135,14 @@ class Pageimage extends Pagefile {
*/
protected $error = '';
+ /**
+ * Last Pageimage::size() $options argument
+ *
+ * @var array
+ *
+ */
+ static protected $lastSizeOptions = array();
+
/**
* Construct a new Pageimage
*
@@ -161,6 +172,7 @@ class Pageimage extends Pagefile {
public function __clone() {
$this->imageInfo['width'] = 0;
$this->imageInfo['height'] = 0;
+ $this->extras = array();
parent::__clone();
}
@@ -180,45 +192,6 @@ class Pageimage extends Pagefile {
}
}
- /**
- * Return the web accessible URL to this image files webP dependency, also if no webp copy exists!
- *
- * @return string
- *
- */
- public function webpUrl() {
- $path_parts = pathinfo($this->url);
- $webpUrl = $path_parts['dirname'] . '/' . $path_parts['filename'] . '.webp';
- return $webpUrl;
- }
-
- /**
- * Return the filesystem path to this image files webP dependency
- *
- * @return string
- *
- */
- public function webpFilename() {
- if(!$this->hasWebp()) {
- return '';
- }
- $path_parts = pathinfo($this->filename);
- $webpFilename = $path_parts['dirname'] . '/' . $path_parts['filename'] . '.webp';
- return $webpFilename;
- }
-
- /**
- * Return if this image file has a webP dependency file
- *
- * @return boolean
- *
- */
- public function hasWebp() {
- $path_parts = pathinfo($this->filename());
- $webpFilename = $path_parts['dirname'] . '/' . $path_parts['filename'] . '.webp';
- return is_readable($webpFilename);
- }
-
/**
* Returns the full disk path to the image file
*
@@ -435,17 +408,21 @@ class Pageimage extends Pagefile {
$value = parent::get('src');
if($value === null) $value = $this->url();
break;
- case 'hasWebp':
- $value = $this->hasWebp();
+ case 'webp':
+ $value = $this->webp();
+ break;
+ case 'hasWebp':
+ $value = $this->webp()->exists();
break;
case 'webpUrl':
- case 'webpSrc':
- case 'urlWebp':
- case 'srcWebp':
- $value = $this->webpUrl();
+ $value = $this->webp()->url();
break;
case 'webpFilename':
- $value = $this->webpFilename();
+ $value = $this->webp()->filename();
+ break;
+ case 'debugInfo':
+ if(!$this->pageimageDebugInfo) $this->pageimageDebugInfo = new PageimageDebugInfo($this);
+ $value = $this->pageimageDebugInfo;
break;
default:
$value = parent::get($key);
@@ -596,6 +573,8 @@ class Pageimage extends Pagefile {
* - `focus` (bool): Should resizes that result in crop use focus area if available? (default=true).
* In order for focus to be applicable, resize must include both width and height.
* - `allowOriginal` (bool): Return original if already at width/height? May not be combined with other options. (default=false)
+ * - `webpAdd` (bool): Also create a secondary .webp image variation? (default=false)
+ * - `webpQuality` (int): Quality setting for extra webp images (default=90).
*
* **Possible values for "cropping" option**
*
@@ -640,10 +619,16 @@ class Pageimage extends Pagefile {
public function size($width, $height, $options = array()) {
if($this->wire('hooks')->isHooked('Pageimage::size()')) {
- return $this->__call('size', array($width, $height, $options));
- } else {
- return $this->___size($width, $height, $options);
+ $result = $this->__call('size', array($width, $height, $options));
+ } else {
+ $result = $this->___size($width, $height, $options);
}
+
+ $options['_width'] = $width;
+ $options['_height'] = $height;
+ self::$lastSizeOptions = $options;
+
+ return $result;
}
/**
@@ -707,10 +692,14 @@ class Pageimage extends Pagefile {
);
$this->error = '';
+
+ /** @var WireFileTools $files */
/** @var Config $config */
+ $files = $this->wire('files');
$config = $this->wire('config');
$debug = $config->debug;
$configOptions = $config->imageSizerOptions;
+
if(!is_array($configOptions)) $configOptions = array();
$options = array_merge($defaultOptions, $configOptions, $options);
if($options['cropping'] === 1) $options['cropping'] = true;
@@ -789,36 +778,36 @@ class Pageimage extends Pagefile {
$nameHeight = is_int($options['nameHeight']) ? $options['nameHeight'] : $height;
// i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
- $basename .= '.' . $nameWidth . 'x' . $nameHeight . $crop . $suffixStr . "." . $this->ext();
- $filenameFinal = $this->pagefiles->path() . $basename;
+ $basenameNoExt = $basename . '.' . $nameWidth . 'x' . $nameHeight . $crop . $suffixStr; // basename without ext
+ $basename = $basenameNoExt . '.' . $this->ext(); // basename with ext
+
$filenameUnvalidated = '';
- $exists = file_exists($filenameFinal);
-
- $path_parts = pathinfo($filenameFinal);
- $filenameFinalWebp = $this->pagefiles->path() . $path_parts['filename'] . '.webp';
+ $filenameUnvalidatedWebp = '';
+
+ $filenameFinal = $this->pagefiles->path() . $basename;
+ $filenameFinalExists = file_exists($filenameFinal);
+ $filenameFinalWebp = $this->pagefiles->path() . $basenameNoExt . '.webp';
+
// force new creation if requested webp copy doesn't exist, (regardless if regular variation exists or not)
- if($options['webpAdd'] && !file_exists($filenameFinalWebp)) {
- $options['forceNew'] = true;
- }
+ if($options['webpAdd'] && !file_exists($filenameFinalWebp)) $options['forceNew'] = true;
// create a new resize if it doesn't already exist or forceNew option is set
- if(!$exists && !file_exists($this->filename())) {
+ if(!$filenameFinalExists && !file_exists($this->filename())) {
// no original file exists to create variation from
$this->error = "Original image does not exist to create size variation";
- } else if(!$exists || $options['forceNew']) {
-
+ } else if(!$filenameFinalExists || $options['forceNew']) {
+
// filenameUnvalidated is temporary filename used for resize
$tempDir = $this->pagefiles->page->filesManager()->getTempPath();
$filenameUnvalidated = $tempDir . $basename;
- $path_parts = pathinfo($filenameUnvalidated);
- $filenameUnvalidatedWebp = $tempDir . $path_parts['filename'] . '.webp';
+ $filenameUnvalidatedWebp = $tempDir . basename($filenameFinalWebp);
- if($exists && $options['forceNew']) $this->wire('files')->unlink($filenameFinal, true);
- if(file_exists($filenameFinalWebp) && $options['forceNew']) $this->wire('files')->unlink($filenameFinalWebp, true);
+ if($filenameFinalExists && $options['forceNew']) $files->unlink($filenameFinal, true);
+ if(file_exists($filenameFinalWebp) && $options['forceNew']) $files->unlink($filenameFinalWebp, true);
- if(file_exists($filenameUnvalidated)) $this->wire('files')->unlink($filenameUnvalidated, true);
- if(file_exists($filenameUnvalidatedWebp)) $this->wire('files')->unlink($filenameUnvalidatedWebp, true);
+ if(file_exists($filenameUnvalidated)) $files->unlink($filenameUnvalidated, true);
+ if(file_exists($filenameUnvalidatedWebp)) $files->unlink($filenameUnvalidatedWebp, true);
if(@copy($this->filename(), $filenameUnvalidated)) {
try {
@@ -832,7 +821,7 @@ class Pageimage extends Pagefile {
$engine = $sizer->getEngine();
/* if the current engine installation does not support webp, modify the options param */
- if(isset($options['webpAdd']) && $options['webpAdd'] && !$engine->supported('webp')) {
+ if(!empty($options['webpAdd']) && !$engine->supported('webp')) {
$options['webpAdd'] = false;
$engine->setOptions($options);
}
@@ -851,23 +840,18 @@ class Pageimage extends Pagefile {
}
}
- if($sizer->resize($width, $height) && @rename($filenameUnvalidated, $filenameFinal)) {
- $this->wire('files')->chmod($filenameFinal);
- if($options['webpAdd'] && file_exists(($filenameUnvalidatedWebp)) && @rename($filenameUnvalidatedWebp, $filenameFinalWebp)) {
- $this->wire('files')->chmod($filenameFinalWebp);
+ if($sizer->resize($width, $height) && $files->rename($filenameUnvalidated, $filenameFinal)) {
+ if($options['webpAdd'] && file_exists($filenameUnvalidatedWebp)) {
+ $files->rename($filenameUnvalidatedWebp, $filenameFinalWebp);
}
} else {
$this->error = "ImageSizer::resize($width, $height) failed for $filenameUnvalidated";
}
- $timer = $debug ? Debug::timer($timer) : null;
if($debug) $this->wire('log')->save('image-sizer',
str_replace('ImageSizerEngine', '', $sizer->getEngine()) . ' ' .
- ($this->error ? "FAILED Resize: " : "Resized: ") .
- "$originalName => " .
- basename($filenameFinal) . " " .
- "({$width}x{$height}) $timer secs " .
- "$originalSize => " . filesize($filenameFinal) . " bytes " .
+ ($this->error ? "FAILED Resize: " : "Resized: ") . "$originalName => " . basename($filenameFinal) . " " .
+ "({$width}x{$height}) " . Debug::timer($timer) . " secs $originalSize => " . filesize($filenameFinal) . " bytes " .
"(quality=$options[quality], sharpening=$options[sharpening]) "
);
@@ -885,11 +869,11 @@ class Pageimage extends Pagefile {
// if desired, user can check for property of $pageimage->error to see if an error occurred.
// if an error occurred, that error property will be populated with details
if($this->error) {
- // error condition: unlink copied file
- if(is_file($filenameFinal)) $this->wire('files')->unlink($filenameFinal, true);
- if($filenameUnvalidated && is_file($filenameUnvalidated)) $this->wire('files')->unlink($filenameUnvalidated);
- if(is_file($filenameFinalWebp)) $this->wire('files')->unlink($filenameFinalWebp, true);
- if(is_file($filenameUnvalidatedWebp)) $this->wire('files')->unlink($filenameUnvalidatedWebp, true);
+ // error condition: unlink copied files
+ if($filenameFinal && is_file($filenameFinal)) $files->unlink($filenameFinal, true);
+ if($filenameUnvalidated && is_file($filenameUnvalidated)) $files->unlink($filenameUnvalidated);
+ if($filenameFinalWebp && is_file($filenameFinalWebp)) $files->unlink($filenameFinalWebp, true);
+ if($filenameUnvalidatedWebp && is_file($filenameUnvalidatedWebp)) $files->unlink($filenameUnvalidatedWebp);
// we also tell PW about it for logging and/or admin purposes
$this->error($this->error);
@@ -1668,16 +1652,13 @@ class Pageimage extends Pagefile {
}
if($success) $deletedFiles[] = $filename;
- // Also remove WebP variation, if there exist one
- $path_parts = pathinfo($filename);
- $webp = dirname($filename) . '/' . $path_parts['filename'] . '.webp';
- if(!is_file($webp)) continue;
- if($options['dryRun']) {
- $success = true;
- } else {
- $success = $files->unlink($webp, true);
+ foreach($this->extras() as $extra) {
+ if($options['dryRun']) {
+ $deletedFiles[] = $extra->filename();
+ } else if($extra->unlink()) {
+ $deletedFiles[] = $extra->filename();
+ }
}
- if($success) $deletedFiles[] = $webp;
}
if(!$options['dryRun']) $this->variations = null;
@@ -1728,7 +1709,7 @@ class Pageimage extends Pagefile {
}
/**
- * Copy this Pageimage and any of it's variations to another path
+ * Copy this Pageimage and any of its variations to another path
*
* #pw-internal
*
@@ -1739,10 +1720,8 @@ class Pageimage extends Pagefile {
public function copyToPath($path) {
if(parent::copyToPath($path)) {
foreach($this->getVariations() as $variation) {
- if(is_file($variation->filename)) {
- copy($variation->filename, $path . $variation->basename);
- if($this->config->chmodFile) chmod($path . $variation->basename, octdec($this->config->chmodFile));
- }
+ if(!is_file($variation->filename)) continue;
+ $this->wire('files')->copy($variation->filename, $path);
}
return true;
}
@@ -1906,222 +1885,95 @@ class Pageimage extends Pagefile {
}
}
-
/**
- * Get verbose DebugInfo, optionally with individual options array, @horst
- * (without invoking the magic debug)
+ * Get WebP "extra" version of this Pageimage
+ *
+ * @return PagefileExtra
+ * @since 3.0.132
+ *
+ */
+ public function webp() {
+ $webp = $this->extras('webp');
+ if(!$webp) {
+ $webp = new PagefileExtra($this, 'webp');
+ $this->extras('webp', $webp);
+ $webp->addHookAfter('create', $this, 'hookWebpCreate');
+ }
+ return $webp;
+ }
+
+ /**
+ * Hook to PageimageExtra (.webp) create method
*
- * @param array $options The individual options you also passes with your image variation creation
- * @param bool $returnType 'string'|'array'|'object', default is 'string' and returns markup or plain text
- * @return array|object|string
+ * #pw-internal
+ *
+ * @param HookEvent $event
*
*/
- public function getDebugInfo($options = array(), $returnType = 'string') {
- static $depth = 0;
- $depth++;
-
- // fetch imagesizer, some infos and some options
- $oSizer = new ImageSizer($this->filename, $options);
- $osInfo = $oSizer->getImageInfo(true);
- $finalOptions = $oSizer->getOptions();
-
- // build some info parts and fetch some from parent (pagefile)
- $thumb = array('thumb' => "");
- $original = $this->original ? array('original' => $this->original->basename, 'basename' => $this->basename) : array('original' => '{SELF}', 'basename' => $this->basename);
- $parent = array('files' => array_merge(
- $original,
- parent::__debugInfo(),
- array(
- 'suffix' => isset($finalOptions['suffix']) ? $finalOptions['suffix'] : '',
- 'extension' => $osInfo['extension']
- )
- ));
- // rearange parts
- unset($parent['files']['filesize']);
- $parent['files']['filesize'] = filesize($this->filename);
-
- // VARIATIONS
- if($depth < 2) {
- $variationArray = array();
- $variations = $this->getVariations(array('info' => true, 'verbose' => false));
- foreach($variations as $name) $variationArray[] = $name;
- }
- $depth--;
- unset($variations, $name);
-
- // start collecting the $info
- $info = array_merge($thumb, $parent,
- array('variations' => $variationArray),
- array('imageinfo' => array(
- 'imageType' => $osInfo['info']['imageType'],
- 'mime' => $osInfo['info']['mime'],
- 'width' => $this->width(),
- 'height' => $this->height(),
- 'focus' => $this->hasFocus ? $this->focusStr : NULL,
- 'description' => $parent['files']['description'],
- 'tags' => $parent['files']['tags'],
- ))
- );
- unset($info['files']['tags'], $info['files']['description']);
-
- // beautify the output, remove unnecessary items
- if(isset($info['files']['filedata']) && isset($info['files']['filedata']['focus'])) unset($info['files']['filedata']['focus']);
- if(empty($info['files']['filedata'])) unset($info['files']['filedata']);
- unset($osInfo['info']['mime'], $osInfo['info']['imageType']);
-
- // add the rest from osInfo to the final $info array
- foreach($osInfo['info'] as $k => $v) $info['imageinfo'][$k] = $v;
- $info['imageinfo']['iptcRaw'] = $osInfo['iptcRaw'];
- unset($osInfo, $thumb, $original, $parent);
-
- // WEBP
- $webp = array('webp_copy' => array(
- 'hasWebp' => $this->hasWebp(),
- 'webpUrl' => (!$this->hasWebp() ? NULL : $this->webpUrl),
- 'webpQuality' => (!isset($finalOptions['webpQuality']) ? NULL : $finalOptions['webpQuality']),
- 'filesize' => (!$this->hasWebp() ? 0 : filesize($this->webpFilename())),
- 'savings' => (!$this->hasWebp() ? 0 : intval($info['files']['filesize'] - filesize($this->webpFilename()))),
- 'savings_percent' => (!$this->hasWebp() ? 0 : 100 - intval(filesize($this->webpFilename()) / ($info['files']['filesize'] / 100))),
- ));
-
- // ENGINES
- $a = [];
- $modules = $this->wire('modules');
- $engines = array_merge($oSizer->getEngines(), array('ImageSizerEngineGD'));
- foreach($engines as $moduleName) {
- $configData = $modules->getModuleConfigData($moduleName);
- $priority = isset($configData['enginePriority']) ? (int) $configData['enginePriority'] : 0;
- $a[$moduleName] = "priority {$priority}";
- }
- asort($a, SORT_STRING);
- $enginesArray = array(
- 'neededEngineSupport' => strtoupper($oSizer->getImageInfo()),
- 'installedEngines' => $a,
- 'selectedEngine' => $oSizer->getEngine()->className,
- 'engineWebpSupport' => $oSizer->getEngine()->supported('webp')
- );
- unset($a, $moduleName, $configData, $engines, $priority, $modules, $oSizer);
-
- // merge all into $info
- $info = array_merge($info, $webp,
- array('engines' => $enginesArray),
- // OPTIONS
- array('options_hierarchy' => array(
- 'imageSizerOptions' => $this->wire('config')->imageSizerOptions,
- 'individualOptions' => $options,
- 'finalOptions' => $finalOptions
- )
- )
- );
- unset($variationArray, $webp, $enginesArray, $options, $finalOptions);
-
- // If not in browser environment, remove the thumb image
- if(!isset($_SERVER['HTTP_HOST'])) unset($info['thumb']);
-
- if('array' == $returnType) {
- // return as array
- return $info;
- } else if('object' == $returnType) {
- // return as object
- $object = new \stdClass();
- foreach($info as $group => $array) {
- $object->$group = new \stdClass();
- if('thumb' == $group) {
- $object->$group = $array;
- continue;
- }
- $this->array_to_object($array, $object->$group);
- }
- return $object;
- }
-
- // make a beautyfied var_dump
- $tmp = $info;
- $info = array();
- foreach($tmp as $group => $array) {
- $info[mb_strtoupper($group)] = $array;
- }
- unset($tmp, $group, $array);
- ob_start();
- var_dump($info);
- $content = ob_get_contents();
- ob_end_clean();
- $m = 0;
- preg_match_all('#^(.*)=>#mU', $content, $stack);
- $lines = $stack[1];
- $indents = array_map('strlen', $lines);
- if($indents) $m = max($indents) + 1;
- $content = preg_replace_callback(
- '#^(.*)=>\\n\s+(\S)#Um',
- function($match) use ($m) {
- return $match[1] . str_repeat(' ', ($m - strlen($match[1]) > 1 ? $m - strlen($match[1]) : 1)) . $match[2];
- },
- $content
- );
- $content = preg_replace('#^((\s*).*){$#m', "\\1\n\\2{", $content);
- $content = str_replace(array('
', ''), '', $content); - if(isset($_SERVER['HTTP_HOST'])) { - // build output for HTML - $return = "
{$content}"; - } else { - // output for Console - $return = $content; - } - - return $return; + public function hookWebpCreate(HookEvent $event) { + if(!$this->original) return; + /** @var PagefileExtra $webp */ + $webp = $event->object; + $webp->unlink(); + $options = self::$lastSizeOptions; + $options['webpAdd'] = true; + $this->original->size($options['_width'], $options['_height'], $options); } /** - * Helper method that converts a multidim array to a multidim object for the getDebugInfo method - * - * @param array $array the input array - * @param object $object the initial object, gets passed recursive by reference through all loops - * @param bool $multidim set this to true to avoid multidimensional object - * @return object the final multidim object - * + * Get all extras, add an extra, or get an extra + * + * #pw-internal + * + * @param string $name + * @param PagefileExtra $value + * @return PagefileExtra[] + * @since 3.0.132 + * */ - private function array_to_object($array, &$object, $multidim = true) { - foreach($array as $key => $value) { - if($multidim && is_array($value)) { - $object->$key = new \stdClass(); - $this->array_to_object($value, $object->$key, false); - } else { - $object->$key = $value; - } - } - return $object; + public function extras($name = null, PagefileExtra $value = null) { + if($name) return parent::extras($name, $value); + $extras = parent::extras(); + $extras['webp'] = $this->webp(); + return $extras; } - - /** - * Debug info + * Basic debug info * * @return array * */ public function __debugInfo() { - static $depth = 0; - $depth++; - $info = parent::__debugInfo(); - $info['width'] = $this->width(); - $info['height'] = $this->height(); - $info['suffix'] = $this->suffixStr; - if($this->hasFocus) $info['focus'] = $this->focusStr; - if(isset($info['filedata']) && isset($info['filedata']['focus'])) unset($info['filedata']['focus']); - if(empty($info['filedata'])) unset($info['filedata']); - $original = $this->original; - if($original && $original !== $this) $info['original'] = $original->basename; - if($depth < 2) { - $info['variations'] = array(); - $variations = $this->getVariations(array('info' => true, 'verbose' => false)); - foreach($variations as $name) { - $info['variations'][] = $name; - } - if(empty($info['variations'])) unset($info['variations']); - } - $depth--; - return $info; + return $this->debugInfo->getBasicDebugInfo(); + } + + /** + * Verbose debug info (via @horst) + * + * Optionally with individual options array. + * + * @param array $options The individual options you also passes with your image variation creation + * @param string $returnType 'string'|'array'|'object', default is 'string' and returns markup or plain text + * @return array|object|string + * @since 3.0.132 + * + */ + public function getDebugInfo($options = array(), $returnType = 'string') { + return $this->debugInfo->getVerboseDebugInfo($options, $returnType); + } + + /** + * Get debug info from parent class + * + * #pw-internal + * + * @return array + * @since 3.0.132 + * + */ + public function _parentDebugInfo() { + return parent::__debugInfo(); } } diff --git a/wire/core/PageimageDebugInfo.php b/wire/core/PageimageDebugInfo.php new file mode 100644 index 00000000..9c6d5324 --- /dev/null +++ b/wire/core/PageimageDebugInfo.php @@ -0,0 +1,306 @@ +wire($this); + $this->pageimage = $pageimage; + parent::__construct(); + } + + /** + * Get property + * + * This primarily delegates to the Pageimage object so that its properties can be accessed + * directly from this class. + * + * @param string $key + * @return mixed|null + * + */ + public function get($key) { + $value = $this->pageimage->get($key); + if($value === null) $value = parent::get($key); + return $value; + } + + /** + * Get basic debug info, like that used for Pageimage::__debugInfo() + * + * @return array + * + */ + public function getBasicDebugInfo() { + static $depth = 0; + $depth++; + $info = $this->pageimage->_parentDebugInfo(); + $info['width'] = $this->pageimage->width(); + $info['height'] = $this->pageimage->height(); + $info['suffix'] = $this->pageimage->suffixStr; + if($this->pageimage->hasFocus) $info['focus'] = $this->pageimage->focusStr; + if(isset($info['filedata']) && isset($info['filedata']['focus'])) unset($info['filedata']['focus']); + if(empty($info['filedata'])) unset($info['filedata']); + $original = $this->original; + if($original && $original !== $this) $info['original'] = $original->basename; + if($depth < 2) { + $info['variations'] = array(); + $variations = $this->pageimage->getVariations(array('info' => true, 'verbose' => false)); + foreach($variations as $name) { + $info['variations'][] = $name; + } + if(empty($info['variations'])) unset($info['variations']); + } + $depth--; + return $info; + } + + /** + * Get verbose DebugInfo, optionally with individual options array, @horst + * + * (without invoking the magic debug) + * + * @param array $options The individual options you also passes with your image variation creation + * @param string $returnType 'string'|'array'|'object', default is 'string' and returns markup or plain text + * @return array|object|string + * + */ + public function getVerboseDebugInfo($options = array(), $returnType = 'string') { + static $depth = 0; + $depth++; + + // fetch imagesizer, some infos and some options + $oSizer = new ImageSizer($this->filename, $options); + $this->wire($oSizer); + $osInfo = $oSizer->getImageInfo(true); + $finalOptions = $oSizer->getOptions(); + + // build some info parts and fetch some from parent (pagefile) + $thumbStyle = "max-width:120px; max-height:120px;"; + $thumbStyle .= $this->width >= $this->height ? 'width:100px; height:auto;' : 'height:100px; width:auto;'; + $thumb = array( + 'thumb' => "
', ''), '', $content); + + if($this->wire('config')->cli) { + // output for Console + $return = $content; + } else { + // build output for HTML + $return = "
" . $this->wire('sanitizer')->entities($content) . ""; + } + + return $return; + } + + /** + * Helper method that converts a multidim array to a multidim object for the getDebugInfo method + * + * @param array $array the input array + * @param object $object the initial object, gets passed recursive by reference through all loops + * @param bool $multidim set this to true to avoid multidimensional object + * @return object the final multidim object + * + */ + private function arrayToObject($array, &$object, $multidim = true) { + foreach($array as $key => $value) { + if($multidim && is_array($value)) { + $object->$key = new \stdClass(); + $this->arrayToObject($value, $object->$key, false); + } else { + $object->$key = $value; + } + } + return $object; + } + + +} \ No newline at end of file diff --git a/wire/modules/Image/ImageSizerEngineIMagick/ImageSizerEngineIMagick.module b/wire/modules/Image/ImageSizerEngineIMagick/ImageSizerEngineIMagick.module index 9cecb6a0..f42a52a8 100755 --- a/wire/modules/Image/ImageSizerEngineIMagick/ImageSizerEngineIMagick.module +++ b/wire/modules/Image/ImageSizerEngineIMagick/ImageSizerEngineIMagick.module @@ -35,6 +35,15 @@ class ImageSizerEngineIMagick extends ImageSizerEngine { */ protected $imWebp = null; + /** + * Webp support available? + * + * @var bool|null + * + */ + static protected $webpSupport = null; + + // @todo the following need phpdoc protected $workspaceColorspace; protected $imageFormat; @@ -163,18 +172,12 @@ class ImageSizerEngineIMagick extends ImageSizerEngine { break; case 'webp': - if(!isset($this->wire('config')->webpSupportIM)) { - // only call it once - ob_start(); - phpinfo(INFO_MODULES); - $dump = ob_get_clean(); - $list = array(); - if(preg_match('#ImageMagick supported formats