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' | |
| ); | |
| } | |
| } |