diff --git a/app/code/Magento/Checkout/i18n/en_US.csv b/app/code/Magento/Checkout/i18n/en_US.csv index 4a78f8deae841..85ee4e1f03f0e 100644 --- a/app/code/Magento/Checkout/i18n/en_US.csv +++ b/app/code/Magento/Checkout/i18n/en_US.csv @@ -176,7 +176,7 @@ Summary,Summary "We'll send your order confirmation here.","We'll send your order confirmation here." Payment,Payment "Not yet calculated","Not yet calculated" -"We received your order!","We received your order!" +"The order was not successful!","The order was not successful!" "Thank you for your purchase!","Thank you for your purchase!" "Password", "Password" "Something went wrong while saving the page. Please refresh the page and try again.","Something went wrong while saving the page. Please refresh the page and try again." @@ -184,4 +184,4 @@ Payment,Payment "Items in Cart","Items in Cart" "Close","Close" "Show Cross-sell Items in the Shopping Cart","Show Cross-sell Items in the Shopping Cart" -"You added %1 to your shopping cart.","You added %1 to your shopping cart." \ No newline at end of file +"You added %1 to your shopping cart.","You added %1 to your shopping cart." diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml index 3ab37c2ab6b9f..b815bf74c155a 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml @@ -9,7 +9,7 @@ - We received your order! + The order was not successful! diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml index 0788bbd60291a..884fa47152932 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml @@ -12,9 +12,12 @@ Assert category grid page basic columns values for default category + + + diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml new file mode 100644 index 0000000000000..c9c9a25d8a2a3 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + Assert asset filter placeholder value + + + + + + + + diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index f52799be3b410..b787f6feaf61e 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,10 +9,14 @@
+ + + +
diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml index d68fd4cb7cca8..74633fbb73542 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml @@ -62,12 +62,20 @@ + + + + + + + + - + diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml index e761ef5cd08ba..7e0fa6c477c45 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -54,6 +54,9 @@ + + + diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php new file mode 100644 index 0000000000000..fe4720b4a3e60 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php @@ -0,0 +1,36 @@ +getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName]) && $item[$fieldName] == 1) { + $item[$fieldName] = 'Yes'; + } else { + $item[$fieldName] = 'No'; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php new file mode 100644 index 0000000000000..c6f20c937d5b3 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php @@ -0,0 +1,36 @@ +getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName]) && $item[$fieldName] == 1) { + $item[$fieldName] = 'Yes'; + } else { + $item[$fieldName] = 'No'; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php index efb2ad2f8dae5..dada8ee7acc19 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php @@ -5,8 +5,9 @@ */ namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; -use Magento\Catalog\Helper\Image; -use Magento\Framework\DataObject; +use Magento\Catalog\Model\Category\Image; +use Magento\Catalog\Model\CategoryRepository; +use Magento\Framework\View\Asset\Repository as AssetRepository; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Store\Model\Store; @@ -27,13 +28,32 @@ class Thumbnail extends Column /** * @var Image */ - private $imageHelper; + private $categoryImage; /** + * @var CategoryRepository + */ + private $categoryRepository; + + /** + * @var AssetRepository + */ + private $assetRepository; + + /** + * @var string[] + */ + private $defaultPlaceholder; + + /** + * Thumbnail constructor. * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param StoreManagerInterface $storeManager - * @param Image $image + * @param Image $categoryImage + * @param CategoryRepository $categoryRepository + * @param AssetRepository $assetRepository + * @param array $defaultPlaceholder * @param array $components * @param array $data */ @@ -41,13 +61,19 @@ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, StoreManagerInterface $storeManager, - Image $image, + Image $categoryImage, + CategoryRepository $categoryRepository, + AssetRepository $assetRepository, + array $defaultPlaceholder = [], array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); - $this->imageHelper = $image; $this->storeManager = $storeManager; + $this->categoryImage = $categoryImage; + $this->categoryRepository = $categoryRepository; + $this->assetRepository = $assetRepository; + $this->defaultPlaceholder = $defaultPlaceholder; } /** @@ -55,20 +81,34 @@ public function __construct( * * @param array $dataSource * @return array + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function prepareDataSource(array $dataSource) { - if (isset($dataSource['data']['items'])) { - $fieldName = $this->getData('name'); - foreach ($dataSource['data']['items'] as & $item) { - if (isset($item[$fieldName])) { - $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); - } else { - $category = new DataObject($item); - $imageHelper = $this->imageHelper->init($category, 'product_listing_thumbnail'); - $item[$fieldName . '_src'] = $imageHelper->getUrl(); + if (!isset($dataSource['data']['items'])) { + return $dataSource; + } + + $fieldName = $this->getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName])) { + $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); + continue; + } + + if (isset($item['entity_id'])) { + $src = $this->categoryImage->getUrl( + $this->categoryRepository->get($item['entity_id']) + ); + + if (!empty($src)) { + $item[$fieldName . '_src'] = $src; + continue; } } + + $item[$fieldName . '_src'] = $this->assetRepository->getUrl($this->defaultPlaceholder['image']); } return $dataSource; diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php index 254ebd047c954..d86617e12b8f8 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php @@ -80,54 +80,36 @@ public function prepare() { $options = []; $productIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $productIds = $applied[$this->getName()]; - } - } + $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( + 'current', + $this->context->getNameSpace() + ); + if ($bookmark === null) { + parent::prepare(); + return; } - foreach ($productIds as $id) { - $product = $this->productRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $product->getName(), - 'is_active' => $product->getStatus(), - 'path' => $product->getSku(), - 'optgroup' => false + $applied = $bookmark->getConfig()['current']['filters']['applied']; - ]; + if (isset($applied[$this->getName()])) { + $productIds = $applied[$this->getName()]; } - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); - - $this->wrappedComponent->prepare(); - $productsFilterJsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) - ); - $this->setData('js_config', $productsFilterJsConfig); - - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); - - $this->applyFilter(); - + foreach ($productIds as $id) { + try { + $product = $this->productRepository->getById($id); + $options[] = [ + 'value' => $id, + 'label' => $product->getName(), + 'is_active' => $product->getStatus(), + 'path' => $product->getSku(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + $this->optionsProvider = $options; parent::prepare(); } } diff --git a/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml b/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml index ae01c29928b4a..2aaf5a56cf837 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml @@ -38,4 +38,11 @@ + + + + Magento_MediaGalleryCatalogUi::images/category/placeholder/image.jpg + + + diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml index 9945643ccffef..e12d90b95303b 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml @@ -167,12 +167,12 @@ - + - + diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg new file mode 100644 index 0000000000000..0d5ef7e1bd412 Binary files /dev/null and b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg differ diff --git a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php index 09fea24c8a2a9..66f8caa71d70a 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php +++ b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php @@ -80,52 +80,36 @@ public function prepare() { $options = []; $blockIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $blockIds = $applied[$this->getName()]; - } - } - } - - foreach ($blockIds as $id) { - $block = $this->blockRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $block->getTitle(), - 'is_active' => $block->isActive(), - 'optgroup' => false - ]; + $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( + 'current', + $this->context->getNameSpace() + ); + if ($bookmark === null) { + parent::prepare(); + return; } - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); + $applied = $bookmark->getConfig()['current']['filters']['applied']; - $this->wrappedComponent->prepare(); - $jsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) - ); - $this->setData('js_config', $jsConfig); - - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); + if (isset($applied[$this->getName()])) { + $blockIds = $applied[$this->getName()]; + } - $this->applyFilter(); + foreach ($blockIds as $id) { + try { + $block = $this->blockRepository->getById($id); + $options[] = [ + 'value' => $id, + 'label' => $block->getTitle(), + 'is_active' => $block->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + $this->optionsProvider = $options; parent::prepare(); } } diff --git a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php index 235a77cdcb8c5..78ab1b63d32d1 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php +++ b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php @@ -80,52 +80,35 @@ public function prepare() { $options = []; $pageIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $pageIds = $applied[$this->getName()]; - } - } - } - - foreach ($pageIds as $id) { - $page = $this->pageRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $page->getTitle(), - 'is_active' => $page->isActive(), - 'optgroup' => false - ]; - } - - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); - - $this->wrappedComponent->prepare(); - $pagesFilterjsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) + $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( + 'current', + $this->context->getNameSpace() ); - $this->setData('js_config', $pagesFilterjsConfig); + if ($bookmark === null) { + parent::prepare(); + return; + } - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); + $applied = $bookmark->getConfig()['current']['filters']['applied']; - $this->applyFilter(); + if (isset($applied[$this->getName()])) { + $pageIds = $applied[$this->getName()]; + } + foreach ($pageIds as $id) { + try { + $page = $this->pageRepository->getById($id); + $options[] = [ + 'value' => $id, + 'label' => $page->getTitle(), + 'is_active' => $page->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + $this->optionsProvider = $options; parent::prepare(); } } diff --git a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php similarity index 57% rename from app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php rename to app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php index 317b811df5692..ed8108f012af0 100644 --- a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php @@ -5,15 +5,15 @@ */ declare(strict_types=1); -namespace Magento\MediaGalleryIntegration\Model; +namespace Magento\MediaGalleryIntegration\Plugin; -use Magento\Framework\DataObject; use Magento\MediaGalleryUiApi\Api\ConfigInterface; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; /** - * Provider to get open media gallery dialog URL for WYSIWYG and widgets + * Plugin to get open media gallery dialog URL for WYSIWYG and widgets */ -class OpenDialogUrlProvider extends DataObject +class NewMediaGalleryOpenDialogUrl { /** * @var ConfigInterface @@ -31,10 +31,13 @@ public function __construct(ConfigInterface $config) /** * Get Url based on media gallery configuration * + * @param OpenDialogUrl $subject + * @param string $result + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return string */ - public function getUrl(): string + public function afterGet(OpenDialogUrl $subject, string $result) { - return $this->config->isEnabled() ? 'media_gallery/index/index' : 'cms/wysiwyg_images/index'; + return $this->config->isEnabled() ? 'media_gallery/index/index' : $result; } } diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php similarity index 80% rename from app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php rename to app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php index 7a3316f293879..90f363d6d792b 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php @@ -9,16 +9,16 @@ namespace Magento\MediaGalleryIntegration\Test\Integration\Model; use Magento\Framework\ObjectManagerInterface; -use Magento\MediaGalleryIntegration\Model\OpenDialogUrlProvider; use Magento\MediaGalleryUiApi\Api\ConfigInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; use PHPUnit\Framework\TestCase; /** * Provide tests cover getting correct url based on the config settings. * @magentoAppArea adminhtml */ -class OpenDialogUrlProviderTest extends TestCase +class OpenDialogUrlTest extends TestCase { /** * @var ObjectManagerInterface @@ -26,9 +26,9 @@ class OpenDialogUrlProviderTest extends TestCase private $objectManger; /** - * @var OpenDialogUrlProvider + * @var OpenDialogUrl */ - private $openDialogUrlProvider; + private $openDialogUrl; /** * @inheritdoc @@ -37,8 +37,8 @@ protected function setUp(): void { $this->objectManger = Bootstrap::getObjectManager(); $config = $this->objectManger->create(ConfigInterface::class); - $this->openDialogUrlProvider = $this->objectManger->create( - OpenDialogUrlProvider::class, + $this->openDialogUrl = $this->objectManger->create( + OpenDialogUrl::class, ['config' => $config] ); } @@ -49,7 +49,7 @@ protected function setUp(): void */ public function testWithEnhancedMediaGalleryDisabled(): void { - self::assertEquals('cms/wysiwyg_images/index', $this->openDialogUrlProvider->getUrl()); + self::assertEquals('cms/wysiwyg_images/index', $this->openDialogUrl->get()); } /** @@ -58,6 +58,6 @@ public function testWithEnhancedMediaGalleryDisabled(): void */ public function testWithEnhancedMediaGalleryEnabled(): void { - self::assertEquals('media_gallery/index/index', $this->openDialogUrlProvider->getUrl()); + self::assertEquals('media_gallery/index/index', $this->openDialogUrl->get()); } } diff --git a/app/code/Magento/MediaGalleryIntegration/composer.json b/app/code/Magento/MediaGalleryIntegration/composer.json index c55d6e0b89733..a9709da81222e 100644 --- a/app/code/Magento/MediaGalleryIntegration/composer.json +++ b/app/code/Magento/MediaGalleryIntegration/composer.json @@ -6,7 +6,8 @@ "magento/framework": "*", "magento/module-media-gallery-ui-api": "*", "magento/module-media-gallery-api": "*", - "magento/module-media-gallery-synchronization-api": "*" + "magento/module-media-gallery-synchronization-api": "*", + "magento/module-ui": "*" }, "require-dev": { "magento/module-cms": "*" diff --git a/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml b/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml index 1559a6d7dfcd5..08e83ce6cad88 100644 --- a/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml +++ b/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml @@ -7,9 +7,7 @@ --> - - Magento\MediaGalleryIntegration\Model\OpenDialogUrlProvider - + diff --git a/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php b/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php index d7290f31ee34e..e100a7f852e42 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php @@ -9,7 +9,6 @@ use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; -use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; /** * Get metadata from IPTC block @@ -42,8 +41,8 @@ public function __construct( */ public function execute(string $data): MetadataInterface { - $title = ''; - $description = ''; + $title = null; + $description = null; $keywords = []; if (is_callable('iptcparse')) { @@ -65,7 +64,7 @@ public function execute(string $data): MetadataInterface return $this->metadataFactory->create([ 'title' => $title, 'description' => $description, - 'keywords' => $keywords + 'keywords' => !empty($keywords) ? $keywords : null ]); } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php new file mode 100644 index 0000000000000..b6c32296f3f7d --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -0,0 +1,104 @@ +metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + if (!is_callable('exif_read_data')) { + throw new LocalizedException( + __('exif_read_data() must be enabled in php configuration') + ); + } + + foreach ($file->getSegments() as $segment) { + if ($this->isExifSegment($segment)) { + return $this->getExifData($file->getPath()); + } + } + + return $this->metadataFactory->create([ + 'title' => null, + 'description' => null, + 'keywords' => null + ]); + } + + /** + * Parese exif data from segment + * + * @param string $filePath + */ + private function getExifData(string $filePath): MetadataInterface + { + $title = null; + $description = null; + $keywords = null; + + $data = exif_read_data($filePath); + + if (!empty($data)) { + $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; + $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; + } + + return $this->metadataFactory->create([ + 'title' => $title, + 'description' => $description, + 'keywords' => $keywords + ]); + } + + /** + * Does segment contain Exif data + * + * @param SegmentInterface $segment + * @return bool + */ + private function isExifSegment(SegmentInterface $segment): bool + { + return $segment->getName() === self::EXIF_SEGMENT_NAME + && strncmp( + substr($segment->getData(), self::EXIF_DATA_START_POSITION, 5), + self::EXIF_SEGMENT_START, + 5 + ) == 0; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php index 94ccb400e5e0a..e56993528a041 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php @@ -56,9 +56,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php index 81ff7200c3475..e68c86d35eb97 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php @@ -54,9 +54,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php new file mode 100644 index 0000000000000..09aeaf526443a --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -0,0 +1,97 @@ +metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + if (!is_callable('exif_read_data')) { + throw new LocalizedException( + __('exif_read_data() must be enabled in php configuration') + ); + } + + foreach ($file->getSegments() as $segment) { + if ($this->isExifSegment($segment)) { + return $this->getExifData($segment); + } + } + + return $this->metadataFactory->create([ + 'title' => null, + 'description' => null, + 'keywords' => null + ]); + } + + /** + * Parese exif data from segment + * + * @param SegmentInterface $segment + */ + private function getExifData(SegmentInterface $segment): MetadataInterface + { + $title = null; + $description = null; + $keywords = []; + + $data = exif_read_data('data://image/jpeg;base64,' . base64_encode($segment->getData())); + + if ($data) { + $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; + $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; + } + + return $this->metadataFactory->create([ + 'title' => $title, + 'description' => $description, + 'keywords' => !empty($keywords) ? $keywords : null + ]); + } + + /** + * Does segment contain Exif data + * + * @param SegmentInterface $segment + * @return bool + */ + private function isExifSegment(SegmentInterface $segment): bool + { + return strcmp($segment->getName(), self::EXIF_SEGMENT_NAME) === 0; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php index 83ba554f7bf5d..518697d421474 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php @@ -55,9 +55,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php index 982ccbb20fe2c..ebe96183eb1f2 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php @@ -37,14 +37,14 @@ protected function setUp(): void * @param string $fileName * @param string $title * @param string $description - * @param array $keywords + * @param null|array $keywords * @throws LocalizedException */ public function testExecute( string $fileName, string $title, string $description, - array $keywords + ?array $keywords ): void { $path = realpath(__DIR__ . '/../../_files/' . $fileName); $metadata = $this->extractMetadata->execute($path); @@ -62,6 +62,18 @@ public function testExecute( public function filesProvider(): array { return [ + [ + 'exif_image.png', + 'Exif title png imge', + 'Exif description png imge', + null + ], + [ + 'exif-image.jpeg', + 'Exif Magento title', + 'Exif description metadata', + null + ], [ 'macos-photos.jpeg', 'Title of the magento image', diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg index 144a56dac2d3e..1a345c2d33fdd 100644 Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg and b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg new file mode 100644 index 0000000000000..cfe27433fd9fc Binary files /dev/null and b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png new file mode 100644 index 0000000000000..4a6bf30c2d516 Binary files /dev/null and b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png index 966520f0d0112..95eb45f69b3ea 100644 Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png and b/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png differ diff --git a/app/code/Magento/MediaGalleryMetadata/etc/di.xml b/app/code/Magento/MediaGalleryMetadata/etc/di.xml index d2f1f90510488..4cd9a34e43a93 100644 --- a/app/code/Magento/MediaGalleryMetadata/etc/di.xml +++ b/app/code/Magento/MediaGalleryMetadata/etc/di.xml @@ -112,6 +112,7 @@ Magento\MediaGalleryMetadata\Model\Png\Segment\ReadXmp Magento\MediaGalleryMetadata\Model\Png\Segment\ReadIptc + Magento\MediaGalleryMetadata\Model\Png\Segment\ReadExif @@ -121,6 +122,7 @@ Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadXmp Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadIptc + Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadExif diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 87d477507b680..80b334733ed43 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -12,11 +12,10 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Framework\Filesystem\Driver\File; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; use Magento\MediaGallerySynchronization\Model\GetContentHash; /** @@ -24,11 +23,6 @@ */ class CreateAssetFromFile { - /** - * Date format - */ - private const DATE_FORMAT = 'Y-m-d H:i:s'; - /** * @var Filesystem */ @@ -39,11 +33,6 @@ class CreateAssetFromFile */ private $driver; - /** - * @var TimezoneInterface; - */ - private $date; - /** * @var AssetInterfaceFactory */ @@ -60,35 +49,32 @@ class CreateAssetFromFile private $extractMetadata; /** - * @var SplFileInfoFactory + * @var GetFileInfo */ - private $splFileInfoFactory; + private $getFileInfo; /** * @param Filesystem $filesystem * @param File $driver - * @param TimezoneInterface $date * @param AssetInterfaceFactory $assetFactory * @param GetContentHash $getContentHash * @param ExtractMetadataInterface $extractMetadata - * @param SplFileInfoFactory $splFileInfoFactory + * @param GetFileInfo $getFileInfo */ public function __construct( Filesystem $filesystem, File $driver, - TimezoneInterface $date, AssetInterfaceFactory $assetFactory, GetContentHash $getContentHash, ExtractMetadataInterface $extractMetadata, - SplFileInfoFactory $splFileInfoFactory + GetFileInfo $getFileInfo ) { $this->filesystem = $filesystem; $this->driver = $driver; - $this->date = $date; $this->assetFactory = $assetFactory; $this->getContentHash = $getContentHash; $this->extractMetadata = $extractMetadata; - $this->splFileInfoFactory = $splFileInfoFactory; + $this->getFileInfo = $getFileInfo; } /** @@ -101,7 +87,7 @@ public function __construct( public function execute(string $path): AssetInterface { $absolutePath = $this->getMediaDirectory()->getAbsolutePath($path); - $file = $this->splFileInfoFactory->create($absolutePath); + $file = $this->getFileInfo->execute($absolutePath); [$width, $height] = getimagesize($absolutePath); $metadata = $this->extractMetadata->execute($absolutePath); @@ -110,10 +96,8 @@ public function execute(string $path): AssetInterface [ 'id' => null, 'path' => $path, - 'title' => $metadata->getTitle() ?: $file->getBasename('.' . $file->getExtension()), + 'title' => $metadata->getTitle() ?: $file->getBasename(), 'description' => $metadata->getDescription(), - 'createdAt' => $this->date->date($file->getCTime())->format(self::DATE_FORMAT), - 'updatedAt' => $this->date->date($file->getMTime())->format(self::DATE_FORMAT), 'width' => $width, 'height' => $height, 'hash' => $this->getHash($path), diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php new file mode 100644 index 0000000000000..5e523fd0e905a --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php @@ -0,0 +1,148 @@ +path = $path; + $this->filename = $filename; + $this->extension = $extension; + $this->basename = $basename; + $this->size = $size; + $this->mTime = $mTime; + $this->cTime = $cTime; + } + + /** + * Get path without filename. + * + * @return string + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Get filename. + * + * @return string + */ + public function getFilename(): string + { + return $this->filename; + } + + /** + * Get file extension. + * + * @return string + */ + public function getExtension(): string + { + return $this->extension; + } + + /** + * Get file basename. + * + * @return string + */ + public function getBasename(): string + { + return $this->basename; + } + + /** + * Get file size. + * + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * Get last modified time. + * + * @return int + */ + public function getMTime(): int + { + return $this->mTime; + } + + /** + * Get inode change time. + * + * @return int + */ + public function getCTime(): int + { + return $this->cTime; + } +} diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php new file mode 100644 index 0000000000000..8f9080767d6e3 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php @@ -0,0 +1,52 @@ +fileInfoFactory = $fileInfoFactory; + } + + /** + * Get file information based on path provided. + * + * @param string $path + * @return FileInfo + */ + public function execute(string $path): FileInfo + { + $splFileInfo = new \SplFileInfo($path); + + return $this->fileInfoFactory->create([ + 'path' => $splFileInfo->getPath(), + 'filename' => $splFileInfo->getFilename(), + 'extension' => $splFileInfo->getExtension(), + 'basename' => $splFileInfo->getBasename('.' . $splFileInfo->getExtension()), + 'size' => $splFileInfo->getSize(), + 'mTime' => $splFileInfo->getMTime(), + 'cTime' => $splFileInfo->getCTime() + ]); + } +} diff --git a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php index 5e825d57c5ce7..533d814c9f1d0 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php @@ -12,7 +12,6 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; /** * Create media asset object based on the file information @@ -34,27 +33,19 @@ class GetAssetFromPath */ private $createAssetFromFile; - /** - * @var SplFileInfoFactory - */ - private $splFileInfoFactory; - /** * @param AssetInterfaceFactory $assetFactory * @param GetAssetsByPathsInterface $getMediaGalleryAssetByPath * @param CreateAssetFromFile $createAssetFromFile - * @param SplFileInfoFactory $splFileInfoFactory */ public function __construct( AssetInterfaceFactory $assetFactory, GetAssetsByPathsInterface $getMediaGalleryAssetByPath, - CreateAssetFromFile $createAssetFromFile, - SplFileInfoFactory $splFileInfoFactory + CreateAssetFromFile $createAssetFromFile ) { $this->assetFactory = $assetFactory; $this->getAssetsByPaths = $getMediaGalleryAssetByPath; $this->createAssetFromFile = $createAssetFromFile; - $this->splFileInfoFactory= $splFileInfoFactory; } /** diff --git a/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php b/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php index 81e9629f703f3..eebb172e48202 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; use Magento\MediaGallerySynchronizationApi\Model\ImportFilesInterface; use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; use Psr\Log\LoggerInterface; /** @@ -50,9 +50,9 @@ class SynchronizeFiles implements SynchronizeFilesInterface private $driver; /** - * @var SplFileInfoFactory + * @var GetFileInfo */ - private $splFileInfoFactory; + private $getFileInfo; /** * @var ImportFilesInterface @@ -69,7 +69,7 @@ class SynchronizeFiles implements SynchronizeFilesInterface * @param Filesystem $filesystem * @param DateTime $date * @param LoggerInterface $log - * @param SplFileInfoFactory $splFileInfoFactory + * @param GetFileInfo $getFileInfo * @param GetAssetsByPathsInterface $getAssetsByPaths * @param ImportFilesInterface $importFiles */ @@ -78,7 +78,7 @@ public function __construct( Filesystem $filesystem, DateTime $date, LoggerInterface $log, - SplFileInfoFactory $splFileInfoFactory, + GetFileInfo $getFileInfo, GetAssetsByPathsInterface $getAssetsByPaths, ImportFilesInterface $importFiles ) { @@ -86,7 +86,7 @@ public function __construct( $this->filesystem = $filesystem; $this->date = $date; $this->log = $log; - $this->splFileInfoFactory = $splFileInfoFactory; + $this->getFileInfo = $getFileInfo; $this->getAssetsByPaths = $getAssetsByPaths; $this->importFiles = $importFiles; } @@ -150,7 +150,7 @@ private function getFileModificationTime(string $path): string { return $this->date->gmtDate( self::DATE_FORMAT, - $this->splFileInfoFactory->create($this->getMediaDirectory()->getAbsolutePath($path))->getMTime() + $this->getFileInfo->execute($this->getMediaDirectory()->getAbsolutePath($path))->getMTime() ); } diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php new file mode 100644 index 0000000000000..6b1e8a676d02b --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -0,0 +1,89 @@ +getFileInfo = Bootstrap::getObjectManager()->get(GetFileInfo::class); + } + + /** + * @dataProvider filesProvider + * @param string $file + */ + public function testExecute( + string $file + ): void { + + $path = $this->getImageFilePath($file); + + $fileInfo = $this->getFileInfo->execute($path); + $expectedResult = new \SplFileInfo($path); + $this->assertEquals($expectedResult->getPath(), $fileInfo->getPath()); + $this->assertEquals($expectedResult->getFilename(), $fileInfo->getFilename()); + $this->assertEquals($expectedResult->getExtension(), $fileInfo->getExtension()); + $this->assertEquals( + $expectedResult->getBasename('.' . $expectedResult->getExtension()), + $fileInfo->getBasename() + ); + $this->assertEquals($expectedResult->getSize(), $fileInfo->getSize()); + $this->assertEquals($expectedResult->getMTime(), $fileInfo->getMTime()); + $this->assertEquals($expectedResult->getCTime(), $fileInfo->getCTime()); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function filesProvider(): array + { + return [ + [ + 'magento.jpg', + 'magento_2.jpg' + ] + ]; + } + + /** + * Return image file path + * + * @param string $filename + * @return string + */ + private function getImageFilePath(string $filename): string + { + return dirname(__DIR__, 2) + . DIRECTORY_SEPARATOR + . implode( + DIRECTORY_SEPARATOR, + [ + '_files', + $filename + ] + ); + } +} diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php index df13250eacb5f..9b6c08edbc86d 100644 --- a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php @@ -139,7 +139,7 @@ public function execute() $responseContent['options'][] = [ 'value' => (string) $asset->getId(), 'label' => $asset->getTitle(), - 'path' => $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()) + 'src' => $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()) ]; $responseContent['total'] = count($responseContent['options']); } diff --git a/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php b/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php new file mode 100644 index 0000000000000..40cf7630d9911 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php @@ -0,0 +1,21 @@ + 0, 'label' => __('Yes')], ['value' => 1, 'label' => __('No')]]; + } +} diff --git a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php index ff82b990d2a01..85522c6b07e00 100644 --- a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php +++ b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php @@ -76,18 +76,16 @@ public function execute(int $id, MetadataInterface $data): void $updatedAsset = $this->assetFactory->create( [ + 'id' => $asset->getId(), 'path' => $asset->getPath(), - 'contentType' => $asset->getContentType(), + 'title' => $data->getTitle() ?? $asset->getTitle(), + 'description' => $data->getDescription() ?? $asset->getDescription(), 'width' => $asset->getWidth(), 'height' => $asset->getHeight(), 'size' => $asset->getSize(), - 'id' => $asset->getId(), - 'title' => $data->getTitle() ?? $asset->getTitle(), - 'description' => $data->getDescription() ?? $asset->getDescription(), - 'source' => $asset->getSource(), 'hash' => $asset->getHash(), - 'created_at' => $asset->getCreatedAt(), - 'updated_at' => $asset->getUpdatedAt() + 'contentType' => $asset->getContentType(), + 'source' => $asset->getSource() ] ); diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml new file mode 100644 index 0000000000000..db400ff151ae3 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + Assert asset filter placeholder value + + + + + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml new file mode 100644 index 0000000000000..b2ce726b3bd6c --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + Remove Keywords on the Edit Details panel + + + + + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml new file mode 100644 index 0000000000000..9460d0b339ca4 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml @@ -0,0 +1,24 @@ + + + + + + + Assert that created_at updated_at time NOT equals + + + + + + grabCreatedTime + grabModifietTime + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml new file mode 100644 index 0000000000000..076885ddaf8b6 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml @@ -0,0 +1,24 @@ + + + + + + + Assert that created_at updated_at time are the same for newly uploaded image + + + + + + grabCreatedTime + grabModifietTime + + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml new file mode 100644 index 0000000000000..be9c7e939103d --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + Verifies that the passed comma-separated list of keywords are not present on the View Details panel + + + + + + + + grabKeywords + {{keywords}} + + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml index b8e2f698ccfe8..b0bed4563003e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml @@ -14,6 +14,7 @@ + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml index 32b109f1e0483..da9f773d0f75e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml @@ -25,5 +25,6 @@ + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml index 048739ed3f81d..e63429677fbae 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml @@ -18,6 +18,8 @@ + + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml new file mode 100644 index 0000000000000..f47d6d9202c05 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -0,0 +1,52 @@ + + + + + + + + + <stories value="User checks if the deleted tags are removed from Edit page, Tags field"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1337102/scenarios/5064888"/> + <description value="User checks if changes made on the tags are updated from Edit page, Tags field"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + </before> + <after> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsDeleteActionGroup" stepKey="deleteImage"/> + </after> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="viewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="editImage"/> + <actionGroup ref="AdminMediaGalleryEditAssetAddKeywordActionGroup" stepKey="setKeywords"> + <argument name="keyword" value="UpdatedImageDetails.keyword"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyAddedKeywords"> + <argument name="keywords" value="UpdatedImageDetails.keyword"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="updateImageDetails"/> + <actionGroup ref="AdminMediaGalleryEditAssetRemoveKeywordActionGroup" stepKey="removeKeywords"> + <argument name="keyword" value="{{UpdatedImageDetails.keyword}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveUpdatedImage"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup" stepKey="verifyRemovedKeywords"> + <argument name="keywords" value="{{UpdatedImageDetails.keyword}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml index ede3a452e4ca5..58c6f32b8d72f 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml @@ -29,6 +29,10 @@ <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> <argument name="image" value="ImageUpload"/> </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="clickViewDetails"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup" stepKey="verifyCreatedAndUpdatedAtDate" /> + <wait time="100" stepKey="waitForUpdateTimeToBeGreater"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeViewDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryEditImageDetailsActionGroup" stepKey="editImageDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> <argument name="image" value="UpdatedImageDetails"/> @@ -40,6 +44,7 @@ <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDetailsActionGroup" stepKey="verifyImageDetails"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> + <actionGroup ref="AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup" stepKey="assertUpdatedAtTimeChanged" /> <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDescriptionActionGroup" stepKey="verifyImageDescription"> <argument name="description" value="UpdatedImageDetails.description"/> </actionGroup> diff --git a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php index 273cf9e37554b..e8dc232584adb 100644 --- a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php +++ b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php @@ -15,9 +15,13 @@ use Magento\MediaContentApi\Api\GetContentByAssetIdsInterface; use Magento\Ui\Component\Filters\FilterModifier; use Magento\Ui\Component\Filters\Type\Select; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\Cms\Helper\Wysiwyg\Images; +use Magento\Cms\Model\Wysiwyg\Images\Storage; +use Magento\Ui\Api\BookmarkManagementInterface; /** - * Asset filter + * Asset filter */ class Asset extends Select { @@ -27,14 +31,41 @@ class Asset extends Select private $getContentIdentities; /** + * @var GetAssetsByIdsInterface + */ + private $getAssetsByIds; + + /** + * @var Images + */ + private $images; + + /** + * @var Storage + */ + private $storage; + + /** + * @var BookmarkManagementInterface + */ + private $bookmarkManagement; + + /** + * Constructor + * * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param FilterBuilder $filterBuilder * @param FilterModifier $filterModifier * @param OptionSourceInterface $optionsProvider * @param GetContentByAssetIdsInterface $getContentIdentities + * @param GetAssetsByIdsInterface $getAssetsByIds + * @param BookmarkManagementInterface $bookmarkManagement + * @param Images $images + * @param Storage $storage * @param array $components * @param array $data + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( ContextInterface $context, @@ -43,6 +74,10 @@ public function __construct( FilterModifier $filterModifier, OptionSourceInterface $optionsProvider = null, GetContentByAssetIdsInterface $getContentIdentities, + GetAssetsByIdsInterface $getAssetsByIds, + BookmarkManagementInterface $bookmarkManagement, + Images $images, + Storage $storage, array $components = [], array $data = [] ) { @@ -58,6 +93,89 @@ public function __construct( $data ); $this->getContentIdentities = $getContentIdentities; + $this->getAssetsByIds = $getAssetsByIds; + $this->bookmarkManagement = $bookmarkManagement; + $this->images = $images; + $this->storage = $storage; + } + + /** + * Prepare component configuration + * + * @return void + */ + public function prepare() + { + $options = []; + $assetIds = $this->getAssetIds(); + + if (empty($assetIds)) { + parent::prepare(); + return; + } + + $assets = $this->getAssetsByIds->execute($assetIds); + + foreach ($assets as $asset) { + $assetPath = $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()); + $options[] = [ + 'value' => (string) $asset->getId(), + 'label' => $asset->getTitle(), + 'src' => $assetPath + ]; + } + + $this->optionsProvider = $options; + parent::prepare(); + } + + /** + * Get asset ids from filterData or from bookmarks + */ + private function getAssetIds(): array + { + $assetIds = []; + + if (isset($this->filterData[$this->getName()])) { + $assetIds = $this->filterData[$this->getName()]; + + if (!is_array($assetIds)) { + $assetIds = $this->stringToArray($assetIds); + } + + return $assetIds; + } + + $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( + 'current', + $this->context->getNameSpace() + ); + + if ($bookmark === null) { + return $assetIds; + } + + $applied = $bookmark->getConfig()['current']['filters']['applied']; + + if (isset($applied[$this->getName()])) { + $assetIds = $applied[$this->getName()]; + } + + if (!is_array($assetIds)) { + $assetIds = $this->stringToArray($assetIds); + } + + return $assetIds; + } + + /** + * Converts string array from url-applier to array + * + * @param string $string + */ + private function stringToArray(string $string): array + { + return explode(',', str_replace(['[', ']'], '', $string)); } /** @@ -67,17 +185,20 @@ public function __construct( */ public function applyFilter() { - if (isset($this->filterData[$this->getName()])) { - $ids = is_array($this->filterData[$this->getName()]) - ? $this->filterData[$this->getName()] - : [$this->filterData[$this->getName()]]; - $filter = $this->filterBuilder->setConditionType('in') - ->setField($this->_data['config']['identityColumn']) - ->setValue($this->getEntityIdsByAsset($ids)) - ->create(); - - $this->getContext()->getDataProvider()->addFilter($filter); + if (!isset($this->filterData[$this->getName()])) { + return; } + + $assetIds = $this->filterData[$this->getName()]; + if (!is_array($assetIds)) { + $assetIds = $this->stringToArray($assetIds); + } + + $filter = $this->filterBuilder->setConditionType('in') + ->setField($this->_data['config']['identityColumn']) + ->setValue($this->getEntityIdsByAsset($assetIds)) + ->create(); + $this->getContext()->getDataProvider()->addFilter($filter); } /** diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml index 77544b42e899a..17aa08b5363ca 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml @@ -9,10 +9,10 @@ <system> <section id="system"> <group id="media_gallery" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Enhanced Media Gallery</label> + <label>Media Gallery</label> <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Enabled</label> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <label>Enable Old Media Gallery</label> + <source_model>Magento\MediaGalleryUi\Model\Config\MediaGallery\Yesno</source_model> <config_path>system/media_gallery/enabled</config_path> </field> </group> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js index c7ca95bed863c..ea4de9e1feefa 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js @@ -51,6 +51,7 @@ define([ return; } + this.mediaGalleryEditDetails().keywordsSelect().cacheOptions.plain = []; modalElement.modal('closeModal'); }, @@ -86,7 +87,8 @@ define([ form = modalElement.find('#image-edit-details-form'), imageId = this.imageModel().getSelected().id, keywords = this.mediaGalleryEditDetails().selectedKeywords(), - imageDetails = this.mediaGalleryImageDetails(); + imageDetails = this.mediaGalleryImageDetails(), + imageEditDetails = this.mediaGalleryEditDetails(); if (form.validation('isValid')) { saveDetails( @@ -98,6 +100,7 @@ define([ this.closeModal(); this.imageModel().reloadGrid(); imageDetails.removeCached(imageId); + imageEditDetails.removeCached(imageId); if (imageDetails.isActive()) { imageDetails.showImageDetailsById(imageId); diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js index c31bc848bdc70..e1404a16d7125 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js @@ -223,6 +223,15 @@ define([ } return true; + }, + + /** + * Remove cached image details in edit form + * + * @param {String} id + */ + removeCached: function (id) { + delete this.images[id]; } }); }); diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html index cce859f331d9a..a0d21672eafdb 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html @@ -77,8 +77,7 @@ </div> </if> <ul class="admin__action-multiselect-menu-inner _root" - event="{mousemove: function(data, event){onMousemove($data, $index(), event)}, - scroll: function(data, event){onScrollDown(data, event)}}"> + event="{scroll: function(data, event){onScrollDown(data, event)}}"> <each args="{ data: options, as: 'option'}"> <li class="admin__action-multiselect-menu-inner-item _root" css="{ _parent: $data.optgroup }" @@ -108,9 +107,8 @@ </if> <label class="admin__action-multiselect-label"> <span text="option.label"></span> - <img if="$parent.getPath(option)" - class="admin__action-multiselect-item-path" - attr="{ src: option.path }"/> + <img class="admin__action-multiselect-item-path" + attr="{ src: option.src }"/> </label> </div> <if args="$data.optgroup"> diff --git a/app/code/Magento/Multishipping/i18n/en_US.csv b/app/code/Magento/Multishipping/i18n/en_US.csv index f9ab587c65fa3..0c248bdcc1af3 100644 --- a/app/code/Magento/Multishipping/i18n/en_US.csv +++ b/app/code/Magento/Multishipping/i18n/en_US.csv @@ -87,7 +87,7 @@ Options,Options "Maximum Qty Allowed for Shipping to Multiple Addresses","Maximum Qty Allowed for Shipping to Multiple Addresses" "Review Order","Review Order" "Select Shipping Method","Select Shipping Method" -"We received your order!","We received your order!" +"The order was not successful!","The order was not successful!" "Ship to:","Ship to:" "Error:","Error:" "We are unable to process your request. Please, try again later.","We are unable to process your request. Please, try again later." diff --git a/app/code/Magento/Store/Model/ResourceModel/StoreWebsiteRelation.php b/app/code/Magento/Store/Model/ResourceModel/StoreWebsiteRelation.php index 19bb45de24d64..875c0e6cb3b05 100644 --- a/app/code/Magento/Store/Model/ResourceModel/StoreWebsiteRelation.php +++ b/app/code/Magento/Store/Model/ResourceModel/StoreWebsiteRelation.php @@ -49,9 +49,10 @@ public function getStoreByWebsiteId($websiteId) * * @param int $websiteId * @param bool $available + * @param int|null $storeGroupId * @return array */ - public function getWebsiteStores(int $websiteId, bool $available = false): array + public function getWebsiteStores(int $websiteId, bool $available = false, int $storeGroupId = null): array { $connection = $this->resource->getConnection(); $storeTable = $this->resource->getTableName('store'); @@ -60,6 +61,13 @@ public function getWebsiteStores(int $websiteId, bool $available = false): array $websiteId ); + if ($storeGroupId) { + $storeSelect = $storeSelect->where( + 'group_id = ?', + $storeGroupId + ); + } + if ($available) { $storeSelect = $storeSelect->where( 'is_active = 1' diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/AvailableStoresResolver.php b/app/code/Magento/StoreGraphQl/Model/Resolver/AvailableStoresResolver.php index eedd7e21fa058..5e49a8a0b7ff0 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/AvailableStoresResolver.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/AvailableStoresResolver.php @@ -41,8 +41,12 @@ public function resolve( array $value = null, array $args = null ) { + $storeGroupId = !empty($args['useCurrentGroup']) ? + (int)$context->getExtensionAttributes()->getStore()->getStoreGroupId() : + null; return $this->storeConfigDataProvider->getAvailableStoreConfig( - (int)$context->getExtensionAttributes()->getStore()->getWebsiteId() + (int)$context->getExtensionAttributes()->getStore()->getWebsiteId(), + $storeGroupId ); } } diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php index 6538d87de9780..8378b3bc7a4be 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php @@ -73,11 +73,12 @@ public function getStoreConfigData(StoreInterface $store): array * Get available website stores * * @param int $websiteId + * @param int|null $storeGroupId * @return array */ - public function getAvailableStoreConfig(int $websiteId): array + public function getAvailableStoreConfig(int $websiteId, int $storeGroupId = null): array { - $websiteStores = $this->storeWebsiteRelation->getWebsiteStores($websiteId, true); + $websiteStores = $this->storeWebsiteRelation->getWebsiteStores($websiteId, true, $storeGroupId); $storeCodes = array_column($websiteStores, 'code'); $storeConfigs = $this->storeConfigManager->getStoreConfigs($storeCodes); diff --git a/app/code/Magento/StoreGraphQl/Plugin/LocalizeEmail.php b/app/code/Magento/StoreGraphQl/Plugin/LocalizeEmail.php new file mode 100644 index 0000000000000..f3d3924b15280 --- /dev/null +++ b/app/code/Magento/StoreGraphQl/Plugin/LocalizeEmail.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\StoreGraphQl\Plugin; + +use Magento\Framework\App\AreaInterface; +use Magento\Framework\App\AreaList; +use Magento\Framework\App\State; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Mail\Template\TransportBuilder; +use Magento\Store\Model\App\Emulation; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Emulate the correct store when GraphQL is sending an email + */ +class LocalizeEmail +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var Emulation + */ + private $emulation; + + /** + * @var AreaList + */ + private $areaList; + + /** + * @var State + */ + private $appState; + + /** + * @param StoreManagerInterface $storeManager + * @param Emulation $emulation + * @param AreaList $areaList + * @param State $appState + */ + public function __construct( + StoreManagerInterface $storeManager, + Emulation $emulation, + AreaList $areaList, + State $appState + ) { + $this->storeManager = $storeManager; + $this->emulation = $emulation; + $this->areaList = $areaList; + $this->appState = $appState; + } + + /** + * Emulate the correct store during email preparation + * + * @param TransportBuilder $subject + * @param \Closure $proceed + * @return mixed + * @throws NoSuchEntityException|LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundGetTransport(TransportBuilder $subject, \Closure $proceed) + { + // Load translations for the app + $area = $this->areaList->getArea($this->appState->getAreaCode()); + $area->load(AreaInterface::PART_TRANSLATE); + + $currentStore = $this->storeManager->getStore(); + $this->emulation->startEnvironmentEmulation($currentStore->getId()); + $output = $proceed(); + $this->emulation->stopEnvironmentEmulation(); + + return $output; + } +} diff --git a/app/code/Magento/StoreGraphQl/etc/graphql/di.xml b/app/code/Magento/StoreGraphQl/etc/graphql/di.xml index 3a0143821d8b9..e7f7da57aaebe 100644 --- a/app/code/Magento/StoreGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/StoreGraphQl/etc/graphql/di.xml @@ -23,4 +23,14 @@ </argument> </arguments> </type> + <type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> + <arguments> + <argument name="extendedConfigData" xsi:type="array"> + <item name="use_store_in_url" xsi:type="string">web/url/use_store</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\Mail\Template\TransportBuilder"> + <plugin name="graphQlEmulateEmail" type="Magento\StoreGraphQl\Plugin\LocalizeEmail" /> + </type> </config> diff --git a/app/code/Magento/StoreGraphQl/etc/schema.graphqls b/app/code/Magento/StoreGraphQl/etc/schema.graphqls index d85bac7801f39..1106987cc72c1 100644 --- a/app/code/Magento/StoreGraphQl/etc/schema.graphqls +++ b/app/code/Magento/StoreGraphQl/etc/schema.graphqls @@ -2,7 +2,9 @@ # See COPYING.txt for license details. type Query { storeConfig : StoreConfig @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\StoreConfigResolver") @doc(description: "The store config query") @cache(cacheable: false) - availableStores: [StoreConfig] @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\AvailableStoresResolver") @doc(description: "Get a list of available store views and their config information.") + availableStores( + useCurrentGroup: Boolean @doc(description: "Filter store views by current store group") + ): [StoreConfig] @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\AvailableStoresResolver") @doc(description: "Get a list of available store views and their config information.") } type Website @doc(description: "Website is deprecated because it is should not be used on storefront. The type contains information about a website") { @@ -32,4 +34,5 @@ type StoreConfig @doc(description: "The type contains information about a store secure_base_static_url : String @doc(description: "Secure base static URL for the store") secure_base_media_url : String @doc(description: "Secure base media URL for the store") store_name : String @doc(description: "Name of the store") + use_store_in_url: Boolean @doc(description: "The configuration determines if the store code should be used in the URL") } diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php index 27370cbfbd68c..9961fc41fc70d 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php @@ -8,10 +8,8 @@ namespace Magento\Ui\Component\Form\Element\DataType\Media; -use Magento\Framework\DataObject; - /** - * Basic configuration for OdenDialogUrl + * Basic configuration for OpenDialogUrl */ class OpenDialogUrl { @@ -23,11 +21,11 @@ class OpenDialogUrl private $openDialogUrl; /** - * @param DataObject $url + * @param string $url */ - public function __construct(DataObject $url = null) + public function __construct(string $url = null) { - $this->openDialogUrl = $url; + $this->openDialogUrl = $url ?? self::DEFAULT_OPEN_DIALOG_URL; } /** @@ -37,9 +35,6 @@ public function __construct(DataObject $url = null) */ public function get(): string { - if ($this->openDialogUrl) { - return $this->openDialogUrl->getUrl(); - } - return self::DEFAULT_OPEN_DIALOG_URL; + return $this->openDialogUrl; } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less index ff4f07a983940..b8be2b33a4475 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less @@ -13,6 +13,30 @@ margin-bottom: @indent__base; } + .checkout-agreement.field { + .lib-vendor-prefix-display(); + + &.required { + label:after { + content: none; + } + + .action-show { + &:after { + content: '*'; + .lib-typography( + @_font-size: @form-field-label-asterisk__font-size, + @_color: @form-field-label-asterisk__color, + @_font-family: @form-field-label-asterisk__font-family, + @_font-weight: @form-field-label-asterisk__font-weight, + @_line-height: @form-field-label-asterisk__line-height, + @_font-style: @form-field-label-asterisk__font-style + ); + } + } + } + } + .action-show { &:extend(.abs-action-button-as-link all); vertical-align: baseline; diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php new file mode 100644 index 0000000000000..d8a88c721c9fe --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Helper; + +/** + * Class for comparing arrays recursively + */ +class CompareArraysRecursively +{ + /** + * Compare arrays recursively regardless of nesting. + * Can compare arrays that have both one level and n-level nesting. + * ``` + * [ + * 'products' => [ + * 'items' => [ + * [ + * 'sku' => 'bundle-product', + * 'type_id' => 'bundle', + * 'items' => [ + * [ + * 'title' => 'Bundle Product Items', + * 'sku' => 'bundle-product', + * 'options' => [ + * [ + * 'price' => 2.75, + * 'label' => 'Simple Product', + * 'product' => [ + * 'name' => 'Simple Product', + * 'sku' => 'simple', + * ] + * ] + * ] + * ] + * ]; + * ``` + * + * @param array $expected + * @param array $actual + * @return array + */ + public function execute(array $expected, array $actual): array + { + $diffResult = []; + + foreach ($expected as $key => $value) { + if (array_key_exists($key, $actual)) { + if (is_array($value)) { + $recursiveDiff = $this->execute($value, $actual[$key]); + if (!empty($recursiveDiff)) { + $diffResult[$key] = $recursiveDiff; + } + } else { + if (!in_array($value, $actual, true)) { + $diffResult[$key] = $value; + } + } + } else { + $diffResult[$key] = $value; + } + } + + return $diffResult; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php index bc3869df6a65b..1523bfe957901 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php @@ -9,6 +9,7 @@ use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CompareArraysRecursively; /** * Tests CategoryManagement @@ -19,6 +20,20 @@ class CategoryManagementTest extends WebapiAbstract const SERVICE_NAME = 'catalogCategoryManagementV1'; + /** + * @var CompareArraysRecursively + */ + private $compareArraysRecursively; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + } + /** * Tests getTree operation * @@ -40,8 +55,8 @@ public function testTree($rootCategoryId, $depth, $expected) ] ]; $result = $this->_webApiCall($serviceInfo, $requestData); - $expected = array_replace_recursive($result, $expected); - $this->assertEquals($expected, $result); + $diff = $this->compareArraysRecursively->execute($expected, $result); + self::assertEquals([], $diff, "Actual categories response doesn't equal expected data"); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/AvailableStoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/AvailableStoreConfigTest.php index eaf76e4559557..d762462729234 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/AvailableStoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/AvailableStoreConfigTest.php @@ -87,6 +87,7 @@ public function testDefaultWebsiteAvailableStoreConfigs(): void secure_base_static_url, secure_base_media_url, store_name + use_store_in_url } } QUERY; @@ -126,6 +127,7 @@ public function testNonDefaultWebsiteAvailableStoreConfigs(): void secure_base_static_url, secure_base_media_url, store_name + use_store_in_url } } QUERY; @@ -167,5 +169,99 @@ private function validateStoreConfig(StoreConfigInterface $storeConfig, array $r $this->assertEquals($storeConfig->getSecureBaseStaticUrl(), $responseConfig['secure_base_static_url']); $this->assertEquals($storeConfig->getSecureBaseMediaUrl(), $responseConfig['secure_base_media_url']); $this->assertEquals($store->getName(), $responseConfig['store_name']); + $this->assertEquals($store->isUseStoreInUrl(), $responseConfig['use_store_in_url']); + } + + /** + * @magentoApiDataFixture Magento/Store/_files/second_website_with_four_stores_divided_in_groups.php + * @magentoConfigFixture web/url/use_store 1 + */ + public function testAllStoreConfigsWithCodeInUrlEnabled(): void + { + $storeConfigs = $this->storeConfigManager->getStoreConfigs( + [ + 'fixture_second_store', + 'fixture_third_store', + 'fixture_fourth_store', + 'fixture_fifth_store' + ] + ); + + $query + = <<<QUERY +{ + availableStores(useCurrentGroup:false) { + id, + code, + website_id, + locale, + base_currency_code, + default_display_currency_code, + timezone, + weight_unit, + base_url, + base_link_url, + base_static_url, + base_media_url, + secure_base_url, + secure_base_link_url, + secure_base_static_url, + secure_base_media_url, + store_name + use_store_in_url + } +} +QUERY; + $headerMap = ['Store' => 'fixture_fifth_store']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + + $this->assertArrayHasKey('availableStores', $response); + $this->assertCount(4, $response['availableStores']); + foreach ($response['availableStores'] as $key => $responseConfig) { + $this->validateStoreConfig($storeConfigs[$key], $responseConfig); + $this->assertEquals(true, $responseConfig['use_store_in_url']); + } + } + + /** + * @magentoApiDataFixture Magento/Store/_files/second_website_with_four_stores_divided_in_groups.php + */ + public function testCurrentGroupStoreConfigs(): void + { + $storeConfigs = $this->storeConfigManager->getStoreConfigs(['fixture_fourth_store', 'fixture_fifth_store']); + + $query + = <<<QUERY +{ + availableStores(useCurrentGroup:true) { + id, + code, + website_id, + locale, + base_currency_code, + default_display_currency_code, + timezone, + weight_unit, + base_url, + base_link_url, + base_static_url, + base_media_url, + secure_base_url, + secure_base_link_url, + secure_base_static_url, + secure_base_media_url, + store_name + use_store_in_url + } +} +QUERY; + $headerMap = ['Store' => 'fixture_fifth_store']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + + $this->assertArrayHasKey('availableStores', $response); + $this->assertCount(2, $response['availableStores']); + foreach ($response['availableStores'] as $key => $responseConfig) { + $this->validateStoreConfig($storeConfigs[$key], $responseConfig); + } } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php index 48b27c83ee6e3..dc3971d1896a1 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/ConfigFixture.php @@ -158,9 +158,9 @@ protected function _assignConfigData(TestCase $test) self::ANNOTATION ); foreach ($testAnnotations as $configPathAndValue) { - if (preg_match('/^.+?(?=_store\s)/', $configPathAndValue, $matches)) { + if (preg_match('/^[^\/]+?(?=_store\s)/', $configPathAndValue, $matches)) { $this->setStoreConfigValue($matches ?? [], $configPathAndValue); - } elseif (preg_match('/^.+?(?=_website\s)/', $configPathAndValue, $matches)) { + } elseif (preg_match('/^[^\/]+?(?=_website\s)/', $configPathAndValue, $matches)) { $this->setWebsiteConfigValue($matches ?? [], $configPathAndValue); } else { $this->setGlobalConfigValue($configPathAndValue); diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerTest.php new file mode 100644 index 0000000000000..11ac2695bd80c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerTest.php @@ -0,0 +1,238 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\GraphQl\Service\GraphQlRequest; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use PHPUnit\Framework\TestCase; + +/** + * Test creating a customer through GraphQL + * + * @magentoAppArea graphql + */ +class CreateCustomerTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var GraphQlRequest + */ + private $graphQlRequest; + + /** + * @var SerializerInterface + */ + private $json; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var StoreRepositoryInterface + */ + private $storeRepository; + + public function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->graphQlRequest = $this->objectManager->create(GraphQlRequest::class); + $this->json = $this->objectManager->get(SerializerInterface::class); + + $this->customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class); + $this->storeRepository = $this->objectManager->create(StoreRepositoryInterface::class); + } + + /** + * Test that creating a customer sends an email + */ + public function testCreateCustomerSendsEmail() + { + $query + = <<<QUERY +mutation createAccount { + createCustomer( + input: { + email: "test@magento.com" + firstname: "Test" + lastname: "Magento" + password: "T3stP4assw0rd" + is_subscribed: false + } + ) { + customer { + id + } + } +} +QUERY; + + $response = $this->graphQlRequest->send($query); + $responseData = $this->json->unserialize($response->getContent()); + + // Assert the response of the GraphQL request + $this->assertNull($responseData['data']['createCustomer']['customer']['id']); + + // Verify the customer was created and has the correct data + $customer = $this->customerRepository->get('test@magento.com'); + $this->assertEquals('Test', $customer->getFirstname()); + $this->assertEquals('Magento', $customer->getLastname()); + + /** @var TransportBuilderMock $transportBuilderMock */ + $transportBuilderMock = $this->objectManager->get(TransportBuilderMock::class); + $sentMessage = $transportBuilderMock->getSentMessage(); + + // Verify an email was dispatched to the correct user + $this->assertNotNull($sentMessage); + $this->assertEquals('Test Magento', $sentMessage->getTo()[0]->getName()); + $this->assertEquals('test@magento.com', $sentMessage->getTo()[0]->getEmail()); + + // Assert the email contains the expected content + $this->assertEquals('Welcome to Main Website Store', $sentMessage->getSubject()); + $messageRaw = $sentMessage->getBody()->getParts()[0]->getRawContent(); + $this->assertStringContainsString('Welcome to Main Website Store.', $messageRaw); + } + + /** + * Test that creating a customer on an alternative store sends an email + * + * @magentoDataFixture Magento/CustomerGraphQl/_files/website_store_with_store_view.php + */ + public function testCreateCustomerForStoreSendsEmail() + { + $query + = <<<QUERY +mutation createAccount { + createCustomer( + input: { + email: "test@magento.com" + firstname: "Test" + lastname: "Magento" + password: "T3stP4assw0rd" + is_subscribed: false + } + ) { + customer { + id + } + } +} +QUERY; + + $response = $this->graphQlRequest->send( + $query, + [], + '', + [ + 'Store' => 'test_store_view' + ] + ); + $responseData = $this->json->unserialize($response->getContent()); + + // Assert the response of the GraphQL request + $this->assertNull($responseData['data']['createCustomer']['customer']['id']); + + // Verify the customer was created and has the correct data + $customer = $this->customerRepository->get('test@magento.com'); + $this->assertEquals('Test', $customer->getFirstname()); + $this->assertEquals('Magento', $customer->getLastname()); + $this->assertEquals('Test Store View', $customer->getCreatedIn()); + + $store = $this->storeRepository->getById($customer->getStoreId()); + $this->assertEquals('test_store_view', $store->getCode()); + + /** @var TransportBuilderMock $transportBuilderMock */ + $transportBuilderMock = $this->objectManager->get(TransportBuilderMock::class); + $sentMessage = $transportBuilderMock->getSentMessage(); + + // Verify an email was dispatched to the correct user + $this->assertNotNull($sentMessage); + $this->assertEquals('Test Magento', $sentMessage->getTo()[0]->getName()); + $this->assertEquals('test@magento.com', $sentMessage->getTo()[0]->getEmail()); + + // Assert the email contains the expected content + $this->assertEquals('Welcome to Test Group', $sentMessage->getSubject()); + $messageRaw = $sentMessage->getBody()->getParts()[0]->getRawContent(); + $this->assertStringContainsString('Welcome to Test Group.', $messageRaw); + } + + /** + * Test that creating a customer on an alternative store sends an email in the translated language + * + * @magentoDataFixture Magento/CustomerGraphQl/_files/website_store_with_store_view.php + * @magentoConfigFixture test_store_view_store general/locale/code fr_FR + * @magentoComponentsDir Magento/CustomerGraphQl/_files + */ + public function testCreateCustomerForStoreSendsTranslatedEmail() + { + $query + = <<<QUERY +mutation createAccount { + createCustomer( + input: { + email: "test@magento.com" + firstname: "Test" + lastname: "Magento" + password: "T3stP4assw0rd" + is_subscribed: false + } + ) { + customer { + id + } + } +} +QUERY; + + $response = $this->graphQlRequest->send( + $query, + [], + '', + [ + 'Store' => 'test_store_view' + ] + ); + $responseData = $this->json->unserialize($response->getContent()); + + // Assert the response of the GraphQL request + $this->assertNull($responseData['data']['createCustomer']['customer']['id']); + + // Verify the customer was created and has the correct data + $customer = $this->customerRepository->get('test@magento.com'); + $this->assertEquals('Test', $customer->getFirstname()); + $this->assertEquals('Magento', $customer->getLastname()); + $this->assertEquals('Test Store View', $customer->getCreatedIn()); + + $store = $this->storeRepository->getById($customer->getStoreId()); + $this->assertEquals('test_store_view', $store->getCode()); + + /** @var TransportBuilderMock $transportBuilderMock */ + $transportBuilderMock = $this->objectManager->get(TransportBuilderMock::class); + $sentMessage = $transportBuilderMock->getSentMessage(); + + // Verify an email was dispatched to the correct user + $this->assertNotNull($sentMessage); + $this->assertEquals('Test Magento', $sentMessage->getTo()[0]->getName()); + $this->assertEquals('test@magento.com', $sentMessage->getTo()[0]->getEmail()); + + // Assert the email contains the expected content + $this->assertEquals('Bienvenue sur Test Group', $sentMessage->getSubject()); + $messageRaw = $sentMessage->getBody()->getParts()[0]->getRawContent(); + $this->assertStringContainsString('Bienvenue sur Test Group.', $messageRaw); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/1.csv b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/1.csv new file mode 100644 index 0000000000000..4d4f9c48f7d40 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/1.csv @@ -0,0 +1,2 @@ +"Welcome to %store_name","Bienvenue sur %store_name" +"Welcome to %store_name.","Bienvenue sur %store_name." diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/language.xml b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/language.xml new file mode 100644 index 0000000000000..9926a71910ebe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/language.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** +* Copyright © Magento, Inc. All rights reserved. +* See COPYING.txt for license details. +*/ +--> +<language xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/Language/package.xsd"> + <code>fr_FR</code> + <vendor>french</vendor> + <package>fr_fr</package> + <sort_order>0</sort_order> +</language> diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/registration.php b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/registration.php new file mode 100644 index 0000000000000..95acf0c1487c7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/french/fr_fr/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::LANGUAGE, 'french_fr_fr', __DIR__); diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view.php b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view.php new file mode 100644 index 0000000000000..7407c1e4e9d09 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Store\Api\Data\GroupInterface; +use Magento\Store\Api\Data\GroupInterfaceFactory; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\StoreInterfaceFactory; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Api\Data\WebsiteInterfaceFactory; +use Magento\Store\Model\ResourceModel\Group as GroupResource; +use Magento\Store\Model\ResourceModel\Store as StoreResource; +use Magento\Store\Model\ResourceModel\Website as WebsiteResource; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var WebsiteResource $websiteResource */ +$websiteResource = $objectManager->get(WebsiteResource::class); +/** @var StoreResource $storeResource */ +$storeResource = $objectManager->get(StoreResource::class); +/** @var GroupResource $groupResource */ +$groupResource = $objectManager->get(GroupResource::class); +/** @var WebsiteInterface $website */ +$website = $objectManager->get(WebsiteInterfaceFactory::class)->create(); +$website->setCode('test_website')->setName('Test Website'); +$websiteResource->save($website); +/** @var GroupInterface $storeGroup */ +$storeGroup = $objectManager->get(GroupInterfaceFactory::class)->create(); +$storeGroup->setCode('test_group') + ->setName('Test Group') + ->setWebsite($website); +$groupResource->save($storeGroup); +/* Refresh stores memory cache */ +$storeManager->reinitStores(); + +/** @var StoreInterface $store */ +$store = $objectManager->get(StoreInterfaceFactory::class)->create(); +$store->setCode('test_store_view') + ->setWebsiteId($website->getId()) + ->setGroupId($storeGroup->getId()) + ->setName('Test Store View') + ->setSortOrder(10) + ->setIsActive(1); +$storeResource->save($store); diff --git a/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view_rollback.php b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view_rollback.php new file mode 100644 index 0000000000000..29f7fbd56c402 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerGraphQl/_files/website_store_with_store_view_rollback.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\StoreInterfaceFactory; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Api\Data\WebsiteInterfaceFactory; +use Magento\Store\Model\ResourceModel\Store as StoreResource; +use Magento\Store\Model\ResourceModel\Website as WebsiteResource; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteResource $websiteResource */ +$websiteResource = $objectManager->get(WebsiteResource::class); +/** @var StoreResource $storeResource */ +$storeResource = $objectManager->get(StoreResource::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var WebsiteInterface $website */ +$website = $objectManager->get(WebsiteInterfaceFactory::class)->create(); +$websiteResource->load($website, 'test_website', 'code'); +if ($website->getId()) { + $websiteResource->delete($website); +} +/** @var StoreInterface $store */ +$store = $objectManager->get(StoreInterfaceFactory::class)->create(); +$storeResource->load($store, 'test_store_view', 'code'); +if ($store->getId()) { + $storeResource->delete($store); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups.php b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups.php new file mode 100644 index 0000000000000..56507bcdd6287 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Helper\DefaultCategory; +use Magento\CatalogSearch\Model\Indexer\Fulltext; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Store\Api\Data\GroupInterface; +use Magento\Store\Api\Data\GroupInterfaceFactory; +use Magento\Store\Model\ResourceModel\Group as GroupResource; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Resolver::getInstance()->requireDataFixture('Magento/Store/_files/second_website_with_two_stores.php'); +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $website \Magento\Store\Model\Website */ +$website = $objectManager->create(\Magento\Store\Model\Website::class); +$website->load('test', 'code')->getId(); +$websiteId = $website->getId(); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var DefaultCategory $defaultCategory */ +$defaultCategory = $objectManager->get(DefaultCategory::class); +/** @var GroupInterface $storeGroup */ +$storeGroup = $objectManager->get(GroupInterfaceFactory::class)->create(); +$storeGroup->setCode('second_group') + ->setRootCategoryId($defaultCategory->getId()) + ->setName('second store group') + ->setWebsite($website); +$objectManager->get(GroupResource::class)->save($storeGroup); +/* Refresh stores memory cache */ +$storeManager->reinitStores(); + +$store = $objectManager->create(Store::class); +if (!$store->load('fixture_fourth_store', 'code')->getId()) { + $store->setCode( + 'fixture_fourth_store' + )->setWebsiteId( + $websiteId + )->setGroupId( + $storeGroup->getId() + )->setName( + 'Fixture Fourth Store' + )->setSortOrder( + 6 + )->setIsActive( + 1 + ); + $store->save(); +} + +$store = $objectManager->create(Store::class); +if (!$store->load('fixture_fifth_store', 'code')->getId()) { + $store->setCode( + 'fixture_fifth_store' + )->setWebsiteId( + $websiteId + )->setGroupId( + $storeGroup->getId() + )->setName( + 'Fixture Fifth Store' + )->setSortOrder( + 5 + )->setIsActive( + 1 + ); + $store->save(); +} + +/* Refresh CatalogSearch index */ +/** @var IndexerRegistry $indexerRegistry */ +$indexerRegistry = $objectManager->get(IndexerRegistry::class); +$indexerRegistry->get(Fulltext::INDEXER_ID)->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups_rollback.php b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups_rollback.php new file mode 100644 index 0000000000000..f4281549a0ac8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/_files/second_website_with_four_stores_divided_in_groups_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if ($store->load('fixture_fourth_store', 'code')->getId()) { + $store->delete(); +} + +$store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if ($store->load('fixture_fifth_store', 'code')->getId()) { + $store->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +Resolver::getInstance()->requireDataFixture('Magento/Store/_files/second_website_with_two_stores_rollback.php'); diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Time.php b/lib/internal/Magento/Framework/Data/Form/Element/Time.php index 53d72d704483c..5f67ac4414e99 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Time.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Time.php @@ -114,7 +114,7 @@ public function getElementHtml() 'style', [], <<<style - .select80wide { + select.select.select80wide { width: 80px; } style diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php index 0791c89ab793a..42ffeae2aa883 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php @@ -319,7 +319,7 @@ public function convertConfigTimeToUtc($date, $format = 'Y-m-d H:i:s') throw new LocalizedException( new Phrase( 'The DateTime object timezone needs to be the same as the "%1" timezone in config.', - $this->getConfigTimezone() + [$this->getConfigTimezone()] ) ); } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php index b72995bb39855..09f85567e4c0d 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\Timezone; @@ -208,6 +209,30 @@ public function testDate($expectedResult, $timezone, $date) ); } + /** + * Data provider for testException + * + * @return array + */ + public function getConvertConfigTimeToUTCDataFixtures() + { + return [ + 'datetime' => [ + new \DateTime('2016-10-10 10:00:00', new \DateTimeZone('UTC')) + ] + ]; + } + + /** + * @dataProvider getConvertConfigTimeToUTCDataFixtures + */ + public function testConvertConfigTimeToUtcException($date) + { + $this->expectException(LocalizedException::class); + + $this->getTimezone()->convertConfigTimeToUtc($date); + } + /** * DataProvider for testDate * diff --git a/lib/internal/Magento/Framework/View/Layout.php b/lib/internal/Magento/Framework/View/Layout.php index ebefc6200cdfa..ce8b086dc7b84 100644 --- a/lib/internal/Magento/Framework/View/Layout.php +++ b/lib/internal/Magento/Framework/View/Layout.php @@ -547,8 +547,7 @@ public function renderNonCachedElement($name) if ($this->appState->getMode() === AppState::MODE_DEVELOPER) { throw $e; } - $message = ($e instanceof LocalizedException) ? $e->getLogMessage() : $e->getMessage(); - $this->logger->critical($message); + $this->logger->critical($e); } return $result; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php index 23f35dfab7cc8..31606b55f6519 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php @@ -19,6 +19,7 @@ use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Layout; +use Magento\Framework\View\Layout\BuilderInterface; use Magento\Framework\View\Layout\Data\Structure as LayoutStructure; use Magento\Framework\View\Layout\Element; use Magento\Framework\View\Layout\Generator\Block; @@ -1169,4 +1170,27 @@ public function renderElementDisplayDataProvider(): array [null], ]; } + + /** + * Test render element with exception + * + * @return void + */ + public function testRenderNonCachedElementWithException(): void + { + $exception = new \Exception('Error message'); + + $builderMock = $this->createMock(BuilderInterface::class); + $builderMock->expects($this->once()) + ->method('build') + ->willThrowException($exception); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); + + $model = clone $this->model; + $model->setBuilder($builderMock); + $model->renderNonCachedElement('test_container'); + } }