Temporary fix for placeholder issue
Replace the following files:
module-catalog/Model/Product/Image/ParamsBuilder.php
module-media-storage/Service/ImageResize.php
Temporary fix for placeholder issue
Replace the following files:
module-catalog/Model/Product/Image/ParamsBuilder.php
module-media-storage/Service/ImageResize.php
<?php | |
/** | |
* Copyright © Magento, Inc. All rights reserved. | |
* See COPYING.txt for license details. | |
*/ | |
declare(strict_types=1); | |
namespace Magento\MediaStorage\Service; | |
use Magento\Catalog\Helper\Image as ImageHelper; | |
use Magento\Catalog\Model\Product\Image\ParamsBuilder; | |
use Magento\Catalog\Model\View\Asset\ImageFactory as AssertImageFactory; | |
use Magento\Framework\App\Area; | |
use Magento\Framework\Exception\NotFoundException; | |
use Magento\Framework\Filesystem; | |
use Magento\Framework\Image; | |
use Magento\Framework\Image\Factory as ImageFactory; | |
use Magento\Catalog\Model\Product\Media\ConfigInterface as MediaConfig; | |
use Magento\Framework\App\State; | |
use Magento\Framework\View\ConfigInterface as ViewConfig; | |
use \Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; | |
use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig; | |
use Magento\Theme\Model\ResourceModel\Theme\Collection; | |
use Magento\Framework\App\Filesystem\DirectoryList; | |
/** | |
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) | |
*/ | |
class ImageResize | |
{ | |
/** | |
* @var State | |
*/ | |
private $appState; | |
/** | |
* @var MediaConfig | |
*/ | |
private $imageConfig; | |
/** | |
* @var ProductImage | |
*/ | |
private $productImage; | |
/** | |
* @var ImageFactory | |
*/ | |
private $imageFactory; | |
/** | |
* @var ParamsBuilder | |
*/ | |
private $paramsBuilder; | |
/** | |
* @var ViewConfig | |
*/ | |
private $viewConfig; | |
/** | |
* @var AssertImageFactory | |
*/ | |
private $assertImageFactory; | |
/** | |
* @var ThemeCustomizationConfig | |
*/ | |
private $themeCustomizationConfig; | |
/** | |
* @var Collection | |
*/ | |
private $themeCollection; | |
/** | |
* @var Filesystem | |
*/ | |
private $mediaDirectory; | |
/** | |
* @var Filesystem | |
*/ | |
private $filesystem; | |
/** | |
* @param State $appState | |
* @param MediaConfig $imageConfig | |
* @param ProductImage $productImage | |
* @param ImageFactory $imageFactory | |
* @param ParamsBuilder $paramsBuilder | |
* @param ViewConfig $viewConfig | |
* @param AssertImageFactory $assertImageFactory | |
* @param ThemeCustomizationConfig $themeCustomizationConfig | |
* @param Collection $themeCollection | |
* @param Filesystem $filesystem | |
* @internal param ProductImage $gallery | |
* @SuppressWarnings(PHPMD.ExcessiveParameterList) | |
*/ | |
public function __construct( | |
State $appState, | |
MediaConfig $imageConfig, | |
ProductImage $productImage, | |
ImageFactory $imageFactory, | |
ParamsBuilder $paramsBuilder, | |
ViewConfig $viewConfig, | |
AssertImageFactory $assertImageFactory, | |
ThemeCustomizationConfig $themeCustomizationConfig, | |
Collection $themeCollection, | |
Filesystem $filesystem, | |
\Magento\Store\Model\StoreManagerInterface $storeManager | |
) { | |
$this->appState = $appState; | |
$this->imageConfig = $imageConfig; | |
$this->productImage = $productImage; | |
$this->imageFactory = $imageFactory; | |
$this->paramsBuilder = $paramsBuilder; | |
$this->viewConfig = $viewConfig; | |
$this->assertImageFactory = $assertImageFactory; | |
$this->themeCustomizationConfig = $themeCustomizationConfig; | |
$this->themeCollection = $themeCollection; | |
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); | |
$this->filesystem = $filesystem; | |
$this->storeManager = $storeManager; | |
} | |
/** | |
* Create resized images of different sizes from an original image | |
* @param string $originalImageName | |
* @throws NotFoundException | |
*/ | |
public function resizeFromImageName(string $originalImageName) | |
{ | |
$originalImagePath = $this->mediaDirectory->getAbsolutePath( | |
$this->imageConfig->getMediaPath($originalImageName) | |
); | |
if (!$this->mediaDirectory->isFile($originalImagePath)) { | |
throw new NotFoundException(__('Cannot resize image "%1" - original image not found', $originalImagePath)); | |
} | |
$storeIds = $this->storeManager->getStores(); | |
foreach($storeIds as $store){ | |
foreach ($this->getViewImages($this->getThemesInUse()) as $viewImage) { | |
$storeId = $store->getId(); | |
$this->resize($viewImage, $originalImagePath, $originalImageName, $storeId); | |
} | |
} | |
} | |
/** | |
* Create resized images of different sizes from themes | |
* @param array|null $themes | |
* @return \Generator | |
* @throws NotFoundException | |
*/ | |
public function resizeFromThemes(array $themes = null): \Generator | |
{ | |
$count = $this->productImage->getCountAllProductImages(); | |
if (!$count) { | |
throw new NotFoundException(__('Cannot resize images - product images not found')); | |
} | |
$productImages = $this->productImage->getAllProductImages(); | |
$viewImages = $this->getViewImages($themes ?? $this->getThemesInUse()); | |
foreach ($productImages as $image) { | |
$originalImageName = $image['filepath']; | |
$originalImagePath = $this->mediaDirectory->getAbsolutePath( | |
$this->imageConfig->getMediaPath($originalImageName) | |
); | |
foreach ($viewImages as $viewImage) { | |
$this->resize($viewImage, $originalImagePath, $originalImageName); | |
} | |
yield $originalImageName => $count; | |
} | |
} | |
/** | |
* Search the current theme | |
* @return array | |
*/ | |
private function getThemesInUse(): array | |
{ | |
$themesInUse = []; | |
$registeredThemes = $this->themeCollection->loadRegisteredThemes(); | |
$storesByThemes = $this->themeCustomizationConfig->getStoresByThemes(); | |
$keyType = is_integer(key($storesByThemes)) ? 'getId' : 'getCode'; | |
foreach ($registeredThemes as $registeredTheme) { | |
if (array_key_exists($registeredTheme->$keyType(), $storesByThemes)) { | |
$themesInUse[] = $registeredTheme; | |
} | |
} | |
return $themesInUse; | |
} | |
/** | |
* Get view images data from themes | |
* @param array $themes | |
* @return array | |
*/ | |
private function getViewImages(array $themes): array | |
{ | |
$viewImages = []; | |
/** @var \Magento\Theme\Model\Theme $theme */ | |
foreach ($themes as $theme) { | |
$config = $this->viewConfig->getViewConfig([ | |
'area' => Area::AREA_FRONTEND, | |
'themeModel' => $theme, | |
]); | |
$images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE); | |
foreach ($images as $imageId => $imageData) { | |
$uniqIndex = $this->getUniqueImageIndex($imageData); | |
$imageData['id'] = $imageId; | |
$viewImages[$uniqIndex] = $imageData; | |
} | |
} | |
return $viewImages; | |
} | |
/** | |
* Get unique image index | |
* @param array $imageData | |
* @return string | |
*/ | |
private function getUniqueImageIndex(array $imageData): string | |
{ | |
ksort($imageData); | |
unset($imageData['type']); | |
return md5(json_encode($imageData)); | |
} | |
/** | |
* Make image | |
* @param string $originalImagePath | |
* @param array $imageParams | |
* @return Image | |
*/ | |
private function makeImage(string $originalImagePath, array $imageParams): Image | |
{ | |
$image = $this->imageFactory->create($originalImagePath); | |
$image->keepAspectRatio($imageParams['keep_aspect_ratio']); | |
$image->keepFrame($imageParams['keep_frame']); | |
$image->keepTransparency($imageParams['keep_transparency']); | |
$image->constrainOnly($imageParams['constrain_only']); | |
$image->backgroundColor($imageParams['background']); | |
$image->quality($imageParams['quality']); | |
return $image; | |
} | |
/** | |
* Resize image | |
* @param array $viewImage | |
* @param string $originalImagePath | |
* @param string $originalImageName | |
*/ | |
private function resize(array $viewImage, string $originalImagePath, string $originalImageName, string $storeId) | |
{ | |
$this->paramsBuilder->setStoreId($storeId); | |
$imageParams = $this->paramsBuilder->build($viewImage); | |
$image = $this->makeImage($originalImagePath, $imageParams); | |
$imageAsset = $this->assertImageFactory->create( | |
[ | |
'miscParams' => $imageParams, | |
'filePath' => $originalImageName, | |
] | |
); | |
#https://github.com/magento/magento2/commit/c7930eb37b29c3bc14735d048845278092fca053?diff=unified | |
if (isset($imageParams['watermark_file'])) { | |
if ($imageParams['watermark_height'] !== null) { | |
$image->setWatermarkHeight($imageParams['watermark_height']); | |
} | |
if ($imageParams['watermark_width'] !== null) { | |
$image->setWatermarkWidth($imageParams['watermark_width']); | |
} | |
if ($imageParams['watermark_position'] !== null) { | |
$image->setWatermarkPosition($imageParams['watermark_position']); | |
} | |
if ($imageParams['watermark_image_opacity'] !== null) { | |
$image->setWatermarkImageOpacity($imageParams['watermark_image_opacity']); | |
} | |
$image->watermark($this->getWatermarkFilePath($imageParams['watermark_file'])); | |
} | |
#https://github.com/magento/magento2/commit/c7930eb37b29c3bc14735d048845278092fca053?diff=unified | |
if ($imageParams['image_width'] !== null && $imageParams['image_height'] !== null) { | |
$image->resize($imageParams['image_width'], $imageParams['image_height']); | |
} | |
$image->save($imageAsset->getPath()); | |
} | |
#https://github.com/magento/magento2/commit/c7930eb37b29c3bc14735d048845278092fca053?diff=unified | |
/** | |
* Returns watermark file absolute path | |
* | |
* @param string $file | |
* @return string | |
*/ | |
private function getWatermarkFilePath($file) | |
{ | |
$path = $this->imageConfig->getMediaPath('/watermark/' . $file); | |
return $this->mediaDirectory->getAbsolutePath($path); | |
} | |
#https://github.com/magento/magento2/commit/c7930eb37b29c3bc14735d048845278092fca053?diff=unified | |
} |
<?php | |
/** | |
* Copyright © Magento, Inc. All rights reserved. | |
* See COPYING.txt for license details. | |
*/ | |
declare(strict_types=1); | |
namespace Magento\Catalog\Model\Product\Image; | |
use Magento\Framework\App\Config\ScopeConfigInterface; | |
use Magento\Framework\View\ConfigInterface; | |
use Magento\Store\Model\ScopeInterface; | |
/** | |
* Builds parameters array used to build Image Asset | |
*/ | |
class ParamsBuilder | |
{ | |
/** | |
* @var int | |
*/ | |
private $defaultQuality = 80; | |
/** | |
* @var array | |
*/ | |
private $defaultBackground = [255, 255, 255]; | |
/** | |
* @var int|null | |
*/ | |
private $defaultAngle = null; | |
/** | |
* @var bool | |
*/ | |
private $defaultKeepAspectRatio = true; | |
/** | |
* @var bool | |
*/ | |
private $defaultKeepTransparency = true; | |
/** | |
* @var bool | |
*/ | |
private $defaultConstrainOnly = true; | |
/** | |
* @var ScopeConfigInterface | |
*/ | |
private $scopeConfig; | |
/** | |
* @var ConfigInterface | |
*/ | |
private $viewConfig; | |
private $storeId; | |
/** | |
* @param ScopeConfigInterface $scopeConfig | |
* @param ConfigInterface $viewConfig | |
*/ | |
public function __construct( | |
ScopeConfigInterface $scopeConfig, | |
ConfigInterface $viewConfig | |
) { | |
$this->scopeConfig = $scopeConfig; | |
$this->viewConfig = $viewConfig; | |
} | |
/** | |
* @param array $imageArguments | |
* @return array | |
* @SuppressWarnings(PHPMD.NPathComplexity) | |
* @SuppressWarnings(PHPMD.CyclomaticComplexity) | |
*/ | |
public function build(array $imageArguments): array | |
{ | |
$miscParams = [ | |
'image_type' => $imageArguments['type'] ?? null, | |
'image_height' => $imageArguments['height'] ?? null, | |
'image_width' => $imageArguments['width'] ?? null, | |
]; | |
$overwritten = $this->overwriteDefaultValues($imageArguments); | |
$watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type']) : []; | |
return array_merge($miscParams, $overwritten, $watermark); | |
} | |
public function setStoreId($storeId): void | |
{ | |
$this->storeId = $storeId; | |
} | |
/** | |
* @param array $imageArguments | |
* @return array | |
*/ | |
private function overwriteDefaultValues(array $imageArguments): array | |
{ | |
$frame = $imageArguments['frame'] ?? $this->hasDefaultFrame(); | |
$constrain = $imageArguments['constrain'] ?? $this->defaultConstrainOnly; | |
$aspectRatio = $imageArguments['aspect_ratio'] ?? $this->defaultKeepAspectRatio; | |
$transparency = $imageArguments['transparency'] ?? $this->defaultKeepTransparency; | |
$background = $imageArguments['background'] ?? $this->defaultBackground; | |
$angle = $imageArguments['angle'] ?? $this->defaultAngle; | |
return [ | |
'background' => (array) $background, | |
'angle' => $angle, | |
'quality' => $this->defaultQuality, | |
'keep_aspect_ratio' => (bool) $aspectRatio, | |
'keep_frame' => (bool) $frame, | |
'keep_transparency' => (bool) $transparency, | |
'constrain_only' => (bool) $constrain, | |
]; | |
} | |
/** | |
* @param string $type | |
* @return array | |
*/ | |
private function getWatermark(string $type): array | |
{ | |
$storeId = $this->storeId; | |
$file = $this->scopeConfig->getValue( | |
"design/watermark/{$type}_image", | |
ScopeInterface::SCOPE_STORE | |
,$storeId | |
); | |
if ($file) { | |
$size = $this->scopeConfig->getValue( | |
"design/watermark/{$type}_size", | |
ScopeInterface::SCOPE_STORE | |
,$storeId | |
); | |
$opacity = $this->scopeConfig->getValue( | |
"design/watermark/{$type}_imageOpacity", | |
ScopeInterface::SCOPE_STORE | |
//,$storeId | |
); | |
$position = $this->scopeConfig->getValue( | |
"design/watermark/{$type}_position", | |
ScopeInterface::SCOPE_STORE | |
,$storeId | |
); | |
$width = !empty($size['width']) ? $size['width'] : null; | |
$height = !empty($size['height']) ? $size['height'] : null; | |
return [ | |
'watermark_file' => $file, | |
'watermark_image_opacity' => $opacity, | |
'watermark_position' => $position, | |
'watermark_width' => $width, | |
'watermark_height' => $height | |
]; | |
} | |
return []; | |
} | |
/** | |
* Get frame from product_image_white_borders | |
* @return bool | |
*/ | |
private function hasDefaultFrame(): bool | |
{ | |
return (bool) $this->viewConfig->getViewConfig()->getVarValue( | |
'Magento_Catalog', | |
'product_image_white_borders' | |
); | |
} | |
} |