diff --git a/classes/Tools/Debugging/System/SystemCompatibilityTool.php b/classes/Tools/Debugging/System/SystemCompatibilityTool.php index c98d4f32..1ecfa0bc 100755 --- a/classes/Tools/Debugging/System/SystemCompatibilityTool.php +++ b/classes/Tools/Debugging/System/SystemCompatibilityTool.php @@ -50,6 +50,7 @@ class SystemCompatibilityTool extends Tool { 'wp_update_attachment_metadata', 'wp_handle_upload_prefilter', 'wp_handle_upload', + 'wp_calculate_image_srcset', ]; diff --git a/classes/Tools/Imgix/ImgixTool.php b/classes/Tools/Imgix/ImgixTool.php index 0ac0dd83..ad7cf79d 100755 --- a/classes/Tools/Imgix/ImgixTool.php +++ b/classes/Tools/Imgix/ImgixTool.php @@ -25,6 +25,8 @@ use MediaCloud\Plugin\Wizard\WizardBuilder; use MediaCloud\Vendor\FasterImage\FasterImage; use MediaCloud\Vendor\Imgix\UrlBuilder; +use function MediaCloud\Plugin\Utilities\anyIsSet; +use function MediaCloud\Plugin\Utilities\arrayContainsAny; use function MediaCloud\Plugin\Utilities\arrayPath; if(!defined('ABSPATH')) { @@ -239,6 +241,23 @@ private function buildImgixParams($params, $mimetype = '') { unset($params['padding-width']); unset($params['padding-color']); + if (!empty($this->settings->cropMode) && !anyIsSet($params, 'rect', 'fp-x', 'fp-y')) { + $position = $this->settings->cropPosition; + + if (isset($params['crop'])) { + $cropParts = explode(',', $params['crop']); + if (arrayContainsAny($cropParts, ['faces', 'focalpoint', 'edges', 'entropy'])) { + return $params; + } + + if (arrayContainsAny($cropParts, ['left', 'top', 'right', 'bottom', 'center'])) { + $position = $params['crop']; + } + } + + $params['crop'] = str_replace('position', $position, $this->settings->cropMode); + } + return $params; } @@ -271,6 +290,10 @@ public function buildSizedImage($id, $size) { $imgix->setSignKey($key); } + if (!empty($this->settings->removeQueryVars)) { + $imgix->setIncludeLibraryParam(false); + } + if (isset($size['crop'])) { $is_crop = !empty($size['crop']); } else { @@ -425,6 +448,10 @@ public function buildImage($id, $size, $params = null, $skipParams = false, $mer $imgix->setSignKey($key); } + if (!empty($this->settings->removeQueryVars)) { + $imgix->setIncludeLibraryParam(false); + } + if($size == 'full' && !$newSize) { if(!isset($meta['width']) || !isset($meta['height'])) { return false; @@ -667,7 +694,7 @@ public function buildImage($id, $size, $params = null, $skipParams = false, $mer $params = array_merge($params, $mergeParams); } - if($size && !is_array($size)) { + if($size && !is_array($size) && empty($this->settings->removeQueryVars)) { $params['wpsize'] = $size; } @@ -703,6 +730,10 @@ public function urlForStorageMedia($key, $params = []) { $imgix->setSignKey($key); } + if (!empty($this->settings->removeQueryVars)) { + $imgix->setIncludeLibraryParam(false); + } + return $imgix->createURL(str_replace(['%2F', '%2540', '%40'], ['/', '@', '@'], urlencode($key)), $params); } @@ -1031,7 +1062,11 @@ public function urlForKey($imageKey) { $imgix->setSignKey($key); } - return $imgix->createURL($imageKey, []); + if (!empty($this->settings->removeQueryVars)) { + $imgix->setIncludeLibraryParam(false); + } + + return $imgix->createURL($imageKey, []); } //endregion diff --git a/classes/Tools/Imgix/ImgixToolSettings.php b/classes/Tools/Imgix/ImgixToolSettings.php index 59365ed0..0895ace6 100755 --- a/classes/Tools/Imgix/ImgixToolSettings.php +++ b/classes/Tools/Imgix/ImgixToolSettings.php @@ -32,6 +32,9 @@ * @property bool renderSVG * @property bool detectFaces * @property bool generateThumbnails + * @property bool removeQueryVars + * @property ?string cropMode + * @property ?string cropPosition */ class ImgixToolSettings extends DynamicImagesToolSettings { private $_imgixDomains = null; @@ -50,6 +53,9 @@ class ImgixToolSettings extends DynamicImagesToolSettings { 'generateThumbnails' => ['mcloud-imgix-generate-thumbnails', null, true], 'imageQuality' => ['mcloud-imgix-default-quality', null, null], 'renderSVG' => ['mcloud-imgix-render-svg-files', null, false], + 'removeQueryVars' => ['mcloud-imgix-remove-extra-variables', null, false], + 'cropMode' => ['mcloud-imgix-crop-mode', null, null], + 'cropPosition' => ['mcloud-imgix-crop-position', null, 'center'], ]; public function __construct() { diff --git a/classes/Tools/Storage/StorageContentHooks.php b/classes/Tools/Storage/StorageContentHooks.php index a99781d1..17f7e6a0 100755 --- a/classes/Tools/Storage/StorageContentHooks.php +++ b/classes/Tools/Storage/StorageContentHooks.php @@ -20,6 +20,7 @@ use MediaCloud\Plugin\Tools\Debugging\DebuggingToolSettings; use MediaCloud\Plugin\Utilities\Environment; use MediaCloud\Plugin\Utilities\Logging\Logger; +use function MediaCloud\Plugin\Utilities\anyEmpty; use function MediaCloud\Plugin\Utilities\arrayPath; if (!defined('ABSPATH')) { header('Location: /'); die; } @@ -1074,6 +1075,17 @@ private function replaceImageInContent($id, $data, $content) { //endregion //region Srcset + + private function getAspectRatio($width, $height) { + if ($width < $height) { + return 0; + } else if ($width == $height) { + return 1; + } else { + return 2; + } + } + /** * Filters an image’s ‘srcset’ sources. (https://core.trac.wordpress.org/browser/tags/4.8/src/wp-includes/media.php#L1203) * @@ -1103,79 +1115,63 @@ public function calculateSrcSet($sources, $size_array, $image_src, $image_meta, $attachment_id = apply_filters('wpml_object_id', $attachment_id, 'attachment', true); - if ($this->allSizes == null) { + if (empty($this->allSizes)) { $this->allSizes = ilab_get_image_sizes(); } - if ($size_array[0] < $size_array[1]) { - $srcAspect = 0; - } else if ($size_array[0] == $size_array[1]) { - $srcAspect = 1; - } else { - $srcAspect = 2; + $srcAspect = $this->getAspectRatio($size_array[0], $size_array[1]); + + $imageWidth = intval($image_meta['width']); + $imageHeight = intval($image_meta['height']); + + if (anyEmpty($imageWidth, $imageHeight)) { + return []; } $allSizesNames = array_keys($this->allSizes); + $newSources = []; foreach($image_meta['sizes'] as $sizeName => $sizeData) { - $width = intval($sizeData['width']); - $height = intval($sizeData['height']); + $width = intval($this->allSizes[$sizeName]['width']); + $height = intval($this->allSizes[$sizeName]['height']); - if ($width < $height) { - $sizeAspect = 0; - } else if ($width == $height) { - $sizeAspect = 1; + $width = ($width === 0) ? 99999 : $width; + $height = ($height === 0) ? 99999 : $height; + + if (empty($this->allSizes[$sizeName]['crop'])) { + $sizeDim = sizeToFitSize($imageWidth, $imageHeight, $width, $height); } else { - $sizeAspect = 2; + $sizeDim = [$width, $height]; } - if (isset($sources[$width])) { - if (($sizeAspect == $srcAspect) && in_array($sizeName, $allSizesNames)) { - $src = wp_get_attachment_image_src($attachment_id, $sizeName); + $sizeAspect = $this->getAspectRatio($sizeDim[0], $sizeDim[1]); - if(is_array($src)) { - // fix for wpml - $url = preg_replace('/&lang=[aA-zZ0-9]+/m', '', $src[0]); - $sources[$width]['url'] = $url; - } else { - unset($sources[$width]); - } - } else { - unset($sources[$width]); + if (isset($sources["{$sizeDim[0]}"]) && ($sizeAspect == $srcAspect) && in_array($sizeName, $allSizesNames)) { + $src = wp_get_attachment_image_src($attachment_id, $sizeName); + + if(is_array($src)) { + // fix for wpml + $url = preg_replace('/&lang=[aA-zZ0-9]+/m', '', $src[0]); + $newSources["{$sizeDim[0]}"] = $sources["{$sizeDim[0]}"]; + $newSources["{$sizeDim[0]}"]['url'] = $url; } } } - if(isset($image_meta['width'])) { - $width = intval($image_meta['width']); - $height = intval($image_meta['height']); - - if ($width < $height) { - $sizeAspect = 0; - } else if ($width == $height) { - $sizeAspect = 1; - } else { - $sizeAspect = 2; - } + $imageAspect = $this->getAspectRatio($imageWidth, $imageHeight); - if(isset($sources[$width])) { - if ($sizeAspect == $srcAspect) { - $src = wp_get_attachment_image_src($attachment_id, 'full'); + if(isset($sources["{$imageWidth}"]) && ($imageAspect == $srcAspect)) { + $src = wp_get_attachment_image_src($attachment_id, 'full'); - if(is_array($src)) { - // fix for wpml - $url = preg_replace('/&lang=[aA-zZ0-9]+/m', '', $src[0]); - $sources[$width]['url'] = $url; - } else { - unset($sources[$width]); - } - } else { - unset($sources[$width]); - } + if(is_array($src)) { + // fix for wpml + $url = preg_replace('/&lang=[aA-zZ0-9]+/m', '', $src[0]); + $newSources["{$imageWidth}"] = $sources["{$imageWidth}"]; + $newSources["{$imageWidth}"]['url'] = $url; } } - return $sources; + return $newSources; } //endregion } \ No newline at end of file diff --git a/classes/Utilities/Helpers.php b/classes/Utilities/Helpers.php index 8dcde12c..99755400 100755 --- a/classes/Utilities/Helpers.php +++ b/classes/Utilities/Helpers.php @@ -259,6 +259,78 @@ function anyEmpty(...$set) { return false; } + /** + * Insures all items are set + * + * @param array $array + * @param array $set + * + * @return bool + */ + function anyIsSet($array,...$set) { + foreach($set as $item) { + if (isset($array[$item])) { + return true; + } + } + + return false; + } + + /** + * Insures all items are set + + * @param array $array + * @param array $set + * + * @return bool + */ + function allIsSet($array, ...$set) { + foreach($set as $item) { + if (!isset($array[$item])) { + return true; + } + } + + return false; + } + + /** + * Determines if an array contains any of the values in another array + * + * @param $array + * @param $values + * + * @return bool + */ + function arrayContainsAny($array, $values) { + foreach($values as $val) { + if (in_array($val, $array)) { + return true; + } + } + + return false; + } + + /** + * Determines if an array contains all of the values in another array + * + * @param $array + * @param $values + * + * @return bool + */ + function arrayContainsAll($array, $values) { + foreach($values as $val) { + if (!in_array($val, $array)) { + return false; + } + } + + return true; + } + /** * Determines if an array is a keyed array * diff --git a/config/imgix.config.php b/config/imgix.config.php index 38082bab..90d0d0e2 100755 --- a/config/imgix.config.php +++ b/config/imgix.config.php @@ -71,6 +71,35 @@ "title" => "Imgix Image Settings", "doc_link" => 'https://support.mediacloud.press/articles/documentation/imgix/imgix-image-settings', "options" => [ + "mcloud-imgix-crop-mode" => [ + "title" => "Crop Mode", + "description" => "Controls the mode when rendering cropped images. Multiple crop modes means that the crop will first try to center the crop on any detected faces, but if no faces are detected than the next crop mode will be used. Note that any settings specified in the Image Size Manager or Image Editor will override this.", + "type" => "select", + "options" => [ + "" => "Default", + 'faces,position' => 'Faces, Position', + 'faces,edges' => 'Faces, Edges', + 'faces,entropy' => 'Faces, Entropy', + ], + "default" => null + ], + "mcloud-imgix-crop-position" => [ + "title" => "Crop Position", + "description" => "Controls the default position of the crop.", + "type" => "select", + "options" => [ + "center" => "Center", + 'top,left' => "Top Left", + 'top' => 'Top', + 'top,right' => "Top Right", + 'right' => 'Right', + 'bottom,right' => 'Bottom Right', + 'bottom' => 'Bottom', + 'bottom, left' => 'Bottom Left', + 'left' => 'Left', + ], + "default" => 'center' + ], "mcloud-imgix-serve-private-images" => [ "title" => "Serve Private Images", "description" => "When enabled, private images, or image sizes that have had their privacy level set to private, will be rendered through imgix. When disabled, any private images or private image sizes will be served from cloud storage using signed URLs, if enabled.", @@ -119,6 +148,12 @@ "description" => "After each upload Media Cloud will use Imgix's face detection API to detect faces in the image. This can be used with Focus Crop in the image editor, or on the front-end however you choose. Note: If you are relying on this functionality, the better option would be to use the Vision tool. It is more accurate with less false positives. If Vision is enabled, this setting is ignored in favor of Vision's results.", "type" => "checkbox", "default" => false + ], + "mcloud-imgix-remove-extra-variables" => [ + "title" => "Remove Extra Query Variables", + "description" => "Removes extra query variables from the imgix URL such as the ixlib and wpsize variables.", + "type" => "checkbox", + "default" => false ] ] ], diff --git a/helpers/ilab-media-tool-geometry-helpers.php b/helpers/ilab-media-tool-geometry-helpers.php index c954be3d..ce794d36 100755 --- a/helpers/ilab-media-tool-geometry-helpers.php +++ b/helpers/ilab-media-tool-geometry-helpers.php @@ -28,7 +28,7 @@ function sizeToFitSize($innerWidth, $innerHeight, $outerWidth, $outerHeight) { $ratio = $outerHeight / $innerHeight; - return [round($innerWidth * $ratio), $outerHeight]; + return [intval(round($innerWidth * $ratio)), intval($outerHeight)]; } if ($outerHeight <= 0) { @@ -38,16 +38,16 @@ function sizeToFitSize($innerWidth, $innerHeight, $outerWidth, $outerHeight) { $ratio = $outerWidth / $innerWidth; - return [$outerWidth, round($innerHeight * $ratio)]; + return [intval($outerWidth), intval(round($innerHeight * $ratio))]; } $ratioW = $outerWidth / $innerWidth; $ratioH = $outerHeight / $innerHeight; if ($ratioW < $ratioH) { - return [$outerWidth, round($innerHeight * $ratioW)]; + return [intval($outerWidth), intval(round($innerHeight * $ratioW))]; } else { - return [round($innerWidth * $ratioH), $outerHeight]; + return [intval(round($innerWidth * $ratioH)), intval($outerHeight)]; } } @@ -60,9 +60,9 @@ function sizeToFillSize($innerWidth, $innerHeight, $outerWidth, $outerHeight, $p $ratioH = $outerHeight / $innerHeight; if (($ratioW > $ratioH) && ($preserveHeight)) { - return [$outerWidth, round($innerHeight * $ratioW)]; + return [intval($outerWidth), intval(round($innerHeight * $ratioW))]; } else { - return [round($innerWidth * $ratioH),$outerHeight]; + return [intval(round($innerWidth * $ratioH)), intval($outerHeight)]; } } diff --git a/ilab-media-tools.php b/ilab-media-tools.php index ad89dffd..5fafbeaa 100755 --- a/ilab-media-tools.php +++ b/ilab-media-tools.php @@ -5,7 +5,7 @@ Plugin URI: https://github.com/interfacelab/ilab-media-tools Description: Automatically upload media to Amazon S3 and integrate with Imgix, a real-time image processing CDN. Boosts site performance and simplifies workflows. Author: interfacelab -Version: 4.2.22 +Version: 4.2.23 Author URI: http://interfacelab.io */ // Copyright (c) 2016 Interfacelab LLC. All rights reserved. @@ -94,7 +94,7 @@ } // Version Defines -define( 'MEDIA_CLOUD_VERSION', '4.2.22' ); +define( 'MEDIA_CLOUD_VERSION', '4.2.23' ); define( 'MEDIA_CLOUD_INFO_VERSION', '4.0.2' ); define( 'MCLOUD_IS_BETA', false ); // Directory defines diff --git a/readme.txt b/readme.txt index ded5846a..cc03875b 100755 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Requires at least: 4.9 Tested up to: 5.7 License: GPLv3 or later License URI: http://www.gnu.org/licenses/gpl-3.0.html -Stable tag: 4.2.22 +Stable tag: 4.2.23 Requires PHP: 7.1 Automatically store media on Amazon S3, Google Cloud Storage, DigitalOcean Spaces + others. Serve CSS/JS assets through CDNs. Integrate with Imgix. @@ -105,6 +105,12 @@ Imgix is a content delivery network with a twist. In addition to distributing y == Changelog == += 4.2.23 = + +* More fixes for srcset generation. +* Ability to turn off `ixlib` and `wpsize` query parameters for imgix image URLs. To disable these query parameters, toggle *Remove Extra Query Variables* off in Imgix settings. +* You can now specify the default cropping mode and crop origin for imgix images in the *Imgix Settings*. This crop mode and origin will be overridden for manually cropped images or images that have had their crop mode set in the *Image Editor*. + = 4.2.22 = * Fix for srcset generation with Imgix.