From 9565c44b1ca474825731b4ed265724238540f6ad Mon Sep 17 00:00:00 2001 From: Franciszek Wawrzak Date: Fri, 27 Mar 2020 23:55:28 +0100 Subject: [PATCH 01/57] improve exception handling in Layout render --- lib/internal/Magento/Framework/View/Layout.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Layout.php b/lib/internal/Magento/Framework/View/Layout.php index a622006f32a1e..02dbd5e005535 100644 --- a/lib/internal/Magento/Framework/View/Layout.php +++ b/lib/internal/Magento/Framework/View/Layout.php @@ -545,8 +545,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; } From b2b45e679b94cdf526f8a74834246e73c5de185e Mon Sep 17 00:00:00 2001 From: yolouiese Date: Wed, 5 Aug 2020 19:16:31 +0800 Subject: [PATCH 02/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - replaced outdated keywords after saving new details --- .../view/adminhtml/web/js/image/image-actions.js | 4 +++- .../view/adminhtml/web/js/image/image-edit.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) 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..9eea75e8cab91 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 @@ -86,7 +86,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 +99,7 @@ define([ this.closeModal(); this.imageModel().reloadGrid(); imageDetails.removeCached(imageId); + imageEditDetails.removeCached(imageId, keywords); 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..e142fb12aa96a 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,16 @@ define([ } return true; + }, + + /** + * Remove cached image details in edit form + * + * @param {String} id + * @param {String} tags + */ + removeCached: function (id, tags) { + this.images[id].tags = tags; } }); }); From 209018c237afb975a05b9460701f215338a9013c Mon Sep 17 00:00:00 2001 From: Angelo Romano Date: Wed, 5 Aug 2020 18:00:34 +0200 Subject: [PATCH 03/57] Changed misunderstanding title In order failure page "We received your order!" is not appopriate. --- .../Checkout/view/frontend/layout/checkout_onepage_failure.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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! From afdcc93028526e740594a146b252f5047a65ac07 Mon Sep 17 00:00:00 2001 From: Angelo Romano Date: Wed, 5 Aug 2020 18:31:16 +0200 Subject: [PATCH 04/57] Update en_US.csv --- app/code/Magento/Checkout/i18n/en_US.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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." From e2b6d338ed4c978fdc11daa1aca93872160e504b Mon Sep 17 00:00:00 2001 From: Angelo Romano Date: Wed, 5 Aug 2020 18:31:49 +0200 Subject: [PATCH 05/57] Update en_US.csv --- app/code/Magento/Multishipping/i18n/en_US.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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." From 5a0680cd19ec70d110ba995996f6f2785258cfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Wed, 5 Aug 2020 22:42:51 +0200 Subject: [PATCH 06/57] Fix #24060 - Terms and Conditions label doesn't fit longer texts --- .../module/checkout/_checkout-agreements.less | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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; From 36335422453af4abf58859acba7224cabe0f5e43 Mon Sep 17 00:00:00 2001 From: Shankar Konar Date: Fri, 7 Aug 2020 11:09:39 +0530 Subject: [PATCH 07/57] Media Gallery configuration are reversed --- .../SaveBaseCategoryImageInformation.php | 2 +- .../Model/OpenDialogUrlProvider.php | 2 +- .../Plugin/SaveImageInformation.php | 2 +- .../Model/ImageComponentOpenDialogUrlTest.php | 4 ++-- .../Model/OpenDialogUrlProviderTest.php | 4 ++-- .../Model/TinyMceOpenDialogUrlTest.php | 4 ++-- .../WysiwygDefaultConfigOpenDialogUrlTest.php | 4 ++-- .../Plugin/MediaGallerySyncTrigger.php | 2 +- .../Test/Mftf/Data/AdobeStockConfigData.xml | 4 ++-- .../MediaGalleryUi/etc/adminhtml/menu.xml | 18 ++++++++++++++++-- .../MediaGalleryUi/etc/adminhtml/system.xml | 4 ++-- 11 files changed, 32 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php b/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php index d439b53c120cb..67a99bfaa84c2 100644 --- a/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php +++ b/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php @@ -86,7 +86,7 @@ public function __construct( */ public function afterMoveFileFromTmp(ImageUploader $subject, string $imagePath): string { - if (!$this->config->isEnabled()) { + if ($this->config->isEnabled()) { return $imagePath; } diff --git a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php b/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php index 317b811df5692..3834550f2703e 100644 --- a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php +++ b/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php @@ -35,6 +35,6 @@ public function __construct(ConfigInterface $config) */ public function getUrl(): string { - return $this->config->isEnabled() ? 'media_gallery/index/index' : 'cms/wysiwyg_images/index'; + return $this->config->isEnabled() ? 'cms/wysiwyg_images/index' : 'media_gallery/index/index'; } } diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php index fbe35db298b04..4d0e6200ddaad 100644 --- a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php @@ -78,7 +78,7 @@ public function __construct( */ public function afterSave(Uploader $subject, array $result): array { - if (!$this->config->isEnabled()) { + if ($this->config->isEnabled()) { return $result; } diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php index dfeaa3eff56bd..0e5cd070a7eec 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php @@ -50,7 +50,7 @@ protected function setUp(): void /** * Test image open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -61,7 +61,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test image open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php index 7a3316f293879..3973ed132b490 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php @@ -45,7 +45,7 @@ protected function setUp(): void /** * Test getting open dialog url with enhanced media gallery disabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -54,7 +54,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test getting open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php index 81a4dc642cfa0..c15536f6af445 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php @@ -58,7 +58,7 @@ protected function setUp(): void /** * Test image open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -68,7 +68,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test image open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php index aebf5927869d5..df6cbe4fc2c09 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php @@ -58,7 +58,7 @@ protected function setUp(): void /** * Test update wysiwyg editor open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -70,7 +70,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test update wysiwyg editor open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php b/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php index 9583c91184d1a..dfe007fa59add 100644 --- a/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php +++ b/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php @@ -43,7 +43,7 @@ public function afterSave(Value $config, Value $result): Value { if ($result->getPath() === self::MEDIA_GALLERY_CONFIG_VALUE && $result->isValueChanged() - && (int) $result->getValue() === self::MEDIA_GALLERY_ENABLED_VALUE + && (int) $result->getValue() !== self::MEDIA_GALLERY_ENABLED_VALUE ) { $this->publish->execute(); } diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml index e8f394a006104..5e02d81fb82ae 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml @@ -9,10 +9,10 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> system/media_gallery/enabled - 1 + 0 system/media_gallery/enabled - 0 + 1 diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml index 92839aa75ac8b..e7486ccbf5a2f 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml @@ -7,7 +7,21 @@ --> - - + + diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml index 77544b42e899a..1c303ab155dae 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml @@ -9,9 +9,9 @@
- + - + Magento\Config\Model\Config\Source\Yesno system/media_gallery/enabled From 651bd690cb0687565cc2a1e99a57d99be5e8f14c Mon Sep 17 00:00:00 2001 From: Shankar Konar Date: Fri, 7 Aug 2020 13:49:35 +0530 Subject: [PATCH 08/57] fixed MFTF test --- .../MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml index 4749fc4a885b0..14464cf36234c 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml @@ -13,7 +13,7 @@ - + From 11d0a351c4ad83637620c9b7ab00ef68b028b7fa Mon Sep 17 00:00:00 2001 From: janmonteros Date: Fri, 7 Aug 2020 16:26:30 +0800 Subject: [PATCH 09/57] magento/adobe-stock-integration#1711: Use product model instead of data object for catalog image helper init --- .../Ui/Component/Listing/Columns/Thumbnail.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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..7a2662b59d198 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php @@ -6,7 +6,7 @@ namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; use Magento\Catalog\Helper\Image; -use Magento\Framework\DataObject; +use Magento\Catalog\Model\ProductFactory; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Store\Model\Store; @@ -29,11 +29,17 @@ class Thumbnail extends Column */ private $imageHelper; + /** + * @var ProductFactory + */ + private $productFactory; + /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param StoreManagerInterface $storeManager * @param Image $image + * @param ProductFactory $productFactory * @param array $components * @param array $data */ @@ -42,12 +48,14 @@ public function __construct( UiComponentFactory $uiComponentFactory, StoreManagerInterface $storeManager, Image $image, + ProductFactory $productFactory, array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); $this->imageHelper = $image; $this->storeManager = $storeManager; + $this->productFactory = $productFactory; } /** @@ -64,7 +72,7 @@ public function prepareDataSource(array $dataSource) if (isset($item[$fieldName])) { $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); } else { - $category = new DataObject($item); + $category = $this->productFactory->create($item); $imageHelper = $this->imageHelper->init($category, 'product_listing_thumbnail'); $item[$fieldName . '_src'] = $imageHelper->getUrl(); } From 25133c56cf0e0390c11399b78cbe3fb426fdd1be Mon Sep 17 00:00:00 2001 From: janmonteros Date: Fri, 7 Aug 2020 18:36:39 +0800 Subject: [PATCH 10/57] magento/adobe-stock-integration#1711: Use product model instead of data object for catalog image helper init - Apply PR suggestion --- .../Ui/Component/Listing/Columns/Thumbnail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7a2662b59d198..4cacaea0d99bf 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php @@ -72,7 +72,7 @@ public function prepareDataSource(array $dataSource) if (isset($item[$fieldName])) { $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); } else { - $category = $this->productFactory->create($item); + $category = $this->productFactory->create(['data' => $item]); $imageHelper = $this->imageHelper->init($category, 'product_listing_thumbnail'); $item[$fieldName . '_src'] = $imageHelper->getUrl(); } From 8b592ee2005c4c324672b0b3f1f69307384c43c5 Mon Sep 17 00:00:00 2001 From: Shankar Konar Date: Fri, 7 Aug 2020 17:02:14 +0530 Subject: [PATCH 11/57] Enabled old media gallery by default --- app/code/Magento/MediaGalleryUi/etc/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryUi/etc/config.xml b/app/code/Magento/MediaGalleryUi/etc/config.xml index fe8e73c406e59..593b1b8e58fd2 100644 --- a/app/code/Magento/MediaGalleryUi/etc/config.xml +++ b/app/code/Magento/MediaGalleryUi/etc/config.xml @@ -9,7 +9,7 @@ - 0 + 1 From c72447ff51cf095595bbe47b67afe0d45d5b0941 Mon Sep 17 00:00:00 2001 From: janmonteros Date: Fri, 7 Aug 2020 23:33:10 +0800 Subject: [PATCH 12/57] magento/adobe-stock-integration#1711: Use product model instead of data object for catalog image helper init - Revised approach based from pr suggestion --- .../Component/Listing/Columns/Thumbnail.php | 72 +++++++++++++----- .../etc/adminhtml/di.xml | 7 ++ .../web/images/category/placeholder/image.jpg | Bin 0 -> 820 bytes 3 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg 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 4cacaea0d99bf..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\Catalog\Model\ProductFactory; +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,19 +28,32 @@ class Thumbnail extends Column /** * @var Image */ - private $imageHelper; + private $categoryImage; /** - * @var ProductFactory + * @var CategoryRepository */ - private $productFactory; + 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 ProductFactory $productFactory + * @param Image $categoryImage + * @param CategoryRepository $categoryRepository + * @param AssetRepository $assetRepository + * @param array $defaultPlaceholder * @param array $components * @param array $data */ @@ -47,15 +61,19 @@ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, StoreManagerInterface $storeManager, - Image $image, - ProductFactory $productFactory, + 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->productFactory = $productFactory; + $this->categoryImage = $categoryImage; + $this->categoryRepository = $categoryRepository; + $this->assetRepository = $assetRepository; + $this->defaultPlaceholder = $defaultPlaceholder; } /** @@ -63,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 = $this->productFactory->create(['data' => $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/etc/adminhtml/di.xml b/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml index 500ac10f4745a..222cfde1385f7 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/web/images/category/placeholder/image.jpg b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d5ef7e1bd4127242b442957d4f543d7e9e0320e GIT binary patch literal 820 zcmex=pQC5bm;@P_1sVSzVJKr@0Gh?ffCN|=S(%vGxC9uO7+6>s*?5^*A(BA2D;WhQ78Z*r8=IIqIu(^PHgDXVyy;NW#7PG~Frt_R(kX}`^8XeC5715~L1sY) zdxr0g3=X=%;Yw4BeG99LQrGqzdlMh<+Dw2u=+Ts~DvEn%w%iO$l#E`i{lYlvxEG{;H!l`?)%OPo6%iU8eH#(dF3ujaFYBlKorG&bgCyJt3kg z^}fmV_wq5#tLsj%Wo@W5X3Q!+SYYB@towGmr<<)+2g}m1Yl-g;uam`m97&*YcxJH|Zkz=200rfsQ_l~IRY z7hT_YC+-5vsh^)$KlRwp7qV;V^oe>FGt-{#IkxxKx7!)KHDx#DQme%b zkKKY7-eWF}InQ~h=b07haGL$I{i^>A$C$1$i$62pbw8Q${DYcb z^ULDzJ`nWx;_tJcbHKc>va0Uoe}<|9M)z7|Z`GAH+;9Fl|7!hX#%xBG7=bdS`}dgk zbu}M&c3#`SW@V!h6G!Q(sE2ze?|a15Xscmo@voTSp5@b*znA}g&G2WXZeYvlNlUb? zG=H|KDmacaz&u*0r8XVY@oZkHX1ZV+Kupuuzim;|aCq&PSm6ja{T aU%Rit(V)P=#L#g1Pv!paUmfcI-vj^#YZ+<) literal 0 HcmV?d00001 From c30dd6029f88beada07e2a1f95ff74904eb8365b Mon Sep 17 00:00:00 2001 From: "vadim.malesh" Date: Mon, 10 Aug 2020 10:11:43 +0300 Subject: [PATCH 13/57] add unit test --- .../Framework/View/Test/Unit/LayoutTest.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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'); + } } From 2cbe0f065ffe13ff441f1346aee77a06683c644d Mon Sep 17 00:00:00 2001 From: Nazar Klovanych Date: Mon, 10 Aug 2020 12:58:32 +0300 Subject: [PATCH 14/57] Fix date timestamp typo --- app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php index ff82b990d2a01..7712bc088f518 100644 --- a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php +++ b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php @@ -86,8 +86,8 @@ public function execute(int $id, MetadataInterface $data): void 'description' => $data->getDescription() ?? $asset->getDescription(), 'source' => $asset->getSource(), 'hash' => $asset->getHash(), - 'created_at' => $asset->getCreatedAt(), - 'updated_at' => $asset->getUpdatedAt() + 'createdAt' => $asset->getCreatedAt(), + 'updatedAt' => $asset->getUpdatedAt() ] ); From 34b441d10a1a8cd6efe814a53cd05d64b8d614dc Mon Sep 17 00:00:00 2001 From: Nazar Klovanych Date: Mon, 10 Aug 2020 13:04:51 +0300 Subject: [PATCH 15/57] Apply correct sort order --- .../Magento/MediaGalleryUi/Model/UpdateAsset.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php index 7712bc088f518..f81c0449306da 100644 --- a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php +++ b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php @@ -76,18 +76,18 @@ 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(), + 'createdAt' => $asset->getCreatedAt(), + 'updatedAt' => $asset->getUpdatedAt(), '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(), - 'createdAt' => $asset->getCreatedAt(), - 'updatedAt' => $asset->getUpdatedAt() + 'contentType' => $asset->getContentType(), + 'source' => $asset->getSource() ] ); From 088b9009553413398a9cf1e1c887a9988faf69fd Mon Sep 17 00:00:00 2001 From: Nazar Klovanych Date: Mon, 10 Aug 2020 13:38:02 +0300 Subject: [PATCH 16/57] represent the create/updated time of database record --- .../Model/CreateAssetFromFile.php | 16 ---------------- .../Magento/MediaGalleryUi/Model/UpdateAsset.php | 2 -- 2 files changed, 18 deletions(-) diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 3305c5e54b861..a76cb8b3b9971 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -12,7 +12,6 @@ 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; @@ -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 */ @@ -67,7 +56,6 @@ class CreateAssetFromFile /** * @param Filesystem $filesystem * @param File $driver - * @param TimezoneInterface $date * @param AssetInterfaceFactory $assetFactory * @param GetContentHashInterface $getContentHash * @param ExtractMetadataInterface $extractMetadata @@ -76,7 +64,6 @@ class CreateAssetFromFile public function __construct( Filesystem $filesystem, File $driver, - TimezoneInterface $date, AssetInterfaceFactory $assetFactory, GetContentHashInterface $getContentHash, ExtractMetadataInterface $extractMetadata, @@ -84,7 +71,6 @@ public function __construct( ) { $this->filesystem = $filesystem; $this->driver = $driver; - $this->date = $date; $this->assetFactory = $assetFactory; $this->getContentHash = $getContentHash; $this->extractMetadata = $extractMetadata; @@ -112,8 +98,6 @@ public function execute(string $path): AssetInterface 'path' => $path, 'title' => $metadata->getTitle() ?: $file->getBasename('.' . $file->getExtension()), '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/MediaGalleryUi/Model/UpdateAsset.php b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php index f81c0449306da..85522c6b07e00 100644 --- a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php +++ b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php @@ -80,8 +80,6 @@ public function execute(int $id, MetadataInterface $data): void 'path' => $asset->getPath(), 'title' => $data->getTitle() ?? $asset->getTitle(), 'description' => $data->getDescription() ?? $asset->getDescription(), - 'createdAt' => $asset->getCreatedAt(), - 'updatedAt' => $asset->getUpdatedAt(), 'width' => $asset->getWidth(), 'height' => $asset->getHeight(), 'size' => $asset->getSize(), From 24eae9db88feed2ff4f2cd32502ed0a71c3ed5da Mon Sep 17 00:00:00 2001 From: Nazar Klovanych Date: Mon, 10 Aug 2020 15:15:44 +0300 Subject: [PATCH 17/57] Cover changes with mftf tests --- ...tedAtNotEqualsUpdatedAtTimeActionGroup.xml | 24 +++++++++++++++++++ ...UploadedImageDateTimeEqualsActionGroup.xml | 24 +++++++++++++++++++ ...EnhancedMediaGalleryViewDetailsSection.xml | 2 ++ ...daloneMediaGalleryEditImageDetailsTest.xml | 5 ++++ 4 files changed, 55 insertions(+) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml new file mode 100644 index 0000000000000..248c6de468193 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.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/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml new file mode 100644 index 0000000000000..f26931f08586f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.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/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml index 0bcbeb0d7a00f..c2bf6e8f29661 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/AdminStandaloneMediaGalleryEditImageDetailsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml index ede3a452e4ca5..c5247dc21ec63 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 @@ + + + + @@ -40,6 +44,7 @@ + From 8d9d0a5ec0d16701c9fd391ce7c302d747e3d165 Mon Sep 17 00:00:00 2001 From: yolouiese Date: Mon, 10 Aug 2020 22:03:50 +0800 Subject: [PATCH 18/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - covered MFTF test --- ...ancedMediaGalleryVerifyUpdatedTagsTest.xml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml 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..3672f582b0877 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + <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="ImageUpload"/> + </actionGroup> + <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="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyMetadataKeywords"> + <argument name="keywords" value="ImageMetadata.keywords"/> + </actionGroup> + </test> +</tests> From 9cf55e57f115486d44f448976ffabd8ee10cce5a Mon Sep 17 00:00:00 2001 From: jekabs <jekabs@developers-alliance.com> Date: Mon, 10 Aug 2020 18:09:48 +0300 Subject: [PATCH 19/57] magento/web-api-test-recursive-array-comparison -Added a function to WebApi Test framework for recursively comparing two arrays -Modified CategoryManagementTest to remove need for array_replace_recursive --- .../TestFramework/TestCase/WebapiAbstract.php | 56 +++++++++++++++++++ .../Catalog/Api/CategoryManagementTest.php | 4 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 7ccab097d7778..91be839aa56cb 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -766,4 +766,60 @@ protected function assertWebApiCallErrors(array $serviceInfo, array $data, array } } } + + /** + * 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 compareArraysRecursively(array $expected, array $actual): array + { + $diffResult = []; + + foreach ($expected as $key => $value) { + if (array_key_exists($key, $actual)) { + if (is_array($value)) { + $recursiveDiff = $this->compareArraysRecursively($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..965b920319994 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php @@ -40,8 +40,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($expected, $result); + self::assertEquals([], $diff, "Actual categories response doesn't equal expected data"); } /** From 5a500f352734a22f8c0464d400130a8250a633e9 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Tue, 11 Aug 2020 01:59:52 +0800 Subject: [PATCH 20/57] magento/adobe-stock-integration#1727: Introduce internal class wrapping SplFileInfo - implement class wrapping SplFileInfo and modified the files to use the newly added classes --- .../Model/CreateAssetFromFile.php | 14 +- .../Model/Filesystem/FileInfo.php | 296 ++++++++++++++++++ .../Model/Filesystem/GetFileInfo.php | 63 ++++ .../Model/GetAssetFromPath.php | 11 +- .../Model/SynchronizeFiles.php | 14 +- 5 files changed, 374 insertions(+), 24 deletions(-) create mode 100644 app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php create mode 100644 app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 87d477507b680..6f1f05a750085 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -16,7 +16,7 @@ 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; /** @@ -60,9 +60,9 @@ class CreateAssetFromFile private $extractMetadata; /** - * @var SplFileInfoFactory + * @var GetFileInfo */ - private $splFileInfoFactory; + private $getFileInfo; /** * @param Filesystem $filesystem @@ -71,7 +71,7 @@ class CreateAssetFromFile * @param AssetInterfaceFactory $assetFactory * @param GetContentHash $getContentHash * @param ExtractMetadataInterface $extractMetadata - * @param SplFileInfoFactory $splFileInfoFactory + * @param GetFileInfo $getFileInfo */ public function __construct( Filesystem $filesystem, @@ -80,7 +80,7 @@ public function __construct( AssetInterfaceFactory $assetFactory, GetContentHash $getContentHash, ExtractMetadataInterface $extractMetadata, - SplFileInfoFactory $splFileInfoFactory + GetFileInfo $getFileInfo ) { $this->filesystem = $filesystem; $this->driver = $driver; @@ -88,7 +88,7 @@ public function __construct( $this->assetFactory = $assetFactory; $this->getContentHash = $getContentHash; $this->extractMetadata = $extractMetadata; - $this->splFileInfoFactory = $splFileInfoFactory; + $this->getFileInfo = $getFileInfo; } /** @@ -101,7 +101,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); 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..20acefe4ab034 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php @@ -0,0 +1,296 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Model\Filesystem; + +/** + * Class FileInfo + */ +class FileInfo extends \SplFileInfo +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $filename; + + /** + * @var string + */ + private $extension; + + /** + * @var $basename + */ + private $basename; + + /** + * @var string + */ + private $pathname; + + /** + * @var int + */ + private $perms; + + /** + * @var int + */ + private $inode; + + /** + * @var int + */ + private $size; + + /** + * @var int + */ + private $owner; + + /** + * @var int + */ + private $group; + + /** + * @var int + */ + private $aTime; + + /** + * @var int + */ + private $mTime; + + /** + * @var int + */ + private $cTime; + + /** + * @var string + */ + private $type; + + /** + * @var false|string + */ + private $realPath; + + /** + * @var \SplFileInfo + */ + private $fileInfo; + + /** + * @var \SplFileInfo + */ + private $pathInfo; + + /** + * FileInfo constructor. + * @param string $file_name + * @param string $path + * @param string $filename + * @param string $extension + * @param string $basename + * @param string $pathname + * @param int $perms + * @param int $inode + * @param int $size + * @param int $owner + * @param int $group + * @param int $aTime + * @param int $mTime + * @param int $cTime + * @param string $type + * @param false|string $realPath + * @param \SplFileInfo $fileInfo + * @param \SplFileInfo $pathInfo + */ + public function __construct( + string $file_name, + string $path, + string $filename, + string $extension, + string $basename, + string $pathname, + int $perms, + int $inode, + int $size, + int $owner, + int $group, + int $aTime, + int $mTime, + int $cTime, + string $type, + $realPath, + \SplFileInfo $fileInfo, + \SplFileInfo $pathInfo + ) { + parent::__construct($file_name); + $this->path = $path; + $this->filename = $filename; + $this->extension = $extension; + $this->basename = $basename; + $this->pathname = $pathname; + $this->perms = $perms; + $this->inode = $inode; + $this->size = $size; + $this->owner = $owner; + $this->group = $group; + $this->aTime = $aTime; + $this->mTime = $mTime; + $this->cTime = $cTime; + $this->type = $type; + $this->realPath = $realPath; + $this->fileInfo = $fileInfo; + $this->pathInfo = $pathInfo; + } + + /** + * @inheritDoc + */ + public function getPath(): string + { + return $this->path; + } + + /** + * @inheritDoc + */ + public function getFilename(): string + { + return $this->filename; + } + + /** + * @inheritDoc + */ + public function getExtension(): string + { + return $this->extension; + } + + /** + * @inheritDoc + */ + public function getBasename($suffix = null): string + { + return $this->basename; + } + + /** + * @inheritDoc + */ + public function getPathname(): string + { + return $this->pathname; + } + + /** + * @inheritDoc + */ + public function getPerms(): int + { + return $this->perms; + } + + /** + * @inheritDoc + */ + public function getInode(): int + { + return $this->inode; + } + + /** + * @inheritDoc + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @inheritDoc + */ + public function getOwner(): int + { + return $this->owner; + } + + /** + * @inheritDoc + */ + public function getGroup(): int + { + return $this->group; + } + + /** + * @inheritDoc + */ + public function getATime(): int + { + return $this->aTime; + } + + /** + * @inheritDoc + */ + public function getMTime(): int + { + return $this->mTime; + } + + /** + * @inheritDoc + */ + public function getCTime(): int + { + return $this->cTime; + } + + /** + * @inheritDoc + */ + public function getType(): string + { + return $this->type; + } + + /** + * @inheritDoc + */ + public function getRealPath() + { + return $this->realPath; + } + + /** + * @inheritDoc + */ + public function getFileInfo($class_name = null): \SplFileInfo + { + return $this->fileInfo; + } + + /** + * @inheritDoc + */ + public function getPathInfo($class_name = null): \SplFileInfo + { + return $this->pathInfo; + } +} 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..ef382732275bd --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Model\Filesystem; + +use Magento\MediaGallerySynchronization\Model\Filesystem\FileInfoFactory; + +/** + * Get file information + */ +class GetFileInfo +{ + /** + * @var FileInfoFactory + */ + private $fileInfoFactory; + + /** + * GetFileInfo constructor. + * @param FileInfoFactory $fileInfoFactory + */ + public function __construct( + FileInfoFactory $fileInfoFactory + ) { + $this->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([ + 'file_name' => $path, + 'path' => $splFileInfo->getPath(), + 'filename' => $splFileInfo->getFilename(), + 'extension' => $splFileInfo->getExtension(), + 'basename' => $splFileInfo->getBasename(), + 'pathname' => $splFileInfo->getPathname(), + 'perms' => $splFileInfo->getPerms(), + 'inode' => $splFileInfo->getInode(), + 'size' => $splFileInfo->getSize(), + 'owner' => $splFileInfo->getOwner(), + 'group' => $splFileInfo->getGroup(), + 'aTime' => $splFileInfo->getATime(), + 'mTime' => $splFileInfo->getMTime(), + 'cTime' => $splFileInfo->getCTime(), + 'type' => $splFileInfo->getType(), + 'realPath' => $splFileInfo->getRealPath(), + 'fileInfo' => $splFileInfo->getFileInfo(), + 'pathInfo' => $splFileInfo->getPathInfo() + ]); + } +} 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() ); } From 36427dc5802f140edc0d6bdbaa1d78534ff3d7ad Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Tue, 11 Aug 2020 10:15:06 +0800 Subject: [PATCH 21/57] magento/adobe-stock-integration#1711: Use product model instead of data object for catalog image helper init - Update MFTF for image placeholder verification, update branch --- .../AdminAssertCategoryGridPageDetailsActionGroup.xml | 1 + .../Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml index 0788bbd60291a..7507b96cde407 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml @@ -12,6 +12,7 @@ <description>Assert category grid page basic columns values for default category</description> </annotations> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image('1','image')}}" stepKey="assertImageColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.path('1')}}" stepKey="assertPathColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name('1', 'Default Category')}}" stepKey="assertNameColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.displayMode('1', 'PRODUCTS')}}" stepKey="assertDisplayModeColumn"/> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 5267a215c8edd..88e44b1cbd556 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,6 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"> + <element name="image" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Image')]/preceding-sibling::th) +1]//img[contains(@src, '{{imageName}}')]" parameterized="true"/> <element name="path" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Path')]/preceding-sibling::th)]" parameterized="true"/> <element name="name" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Name')]/preceding-sibling::th) +1 ]//*[text()='{{categoryName}}']" parameterized="true"/> <element name="displayMode" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Display Mode')]/preceding-sibling::th) +1 ]//*[text()='{{productsText}}']" parameterized="true"/> From 50aae09ebbf141fa643b476f39bbfb7791ab1296 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 11 Aug 2020 10:23:50 +0300 Subject: [PATCH 22/57] CodeReview suggestions --- ...erifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml | 2 +- ...dMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml} | 2 +- .../AdminStandaloneMediaGalleryEditImageDetailsTest.xml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/{AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml => AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml} (94%) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml index 248c6de468193..9460d0b339ca4 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup"> + <actionGroup name="AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup"> <annotations> <description>Assert that created_at updated_at time NOT equals</description> </annotations> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml similarity index 94% rename from app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml rename to app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml index f26931f08586f..076885ddaf8b6 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup"> + <actionGroup name="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup"> <annotations> <description>Assert that created_at updated_at time are the same for newly uploaded image </description> </annotations> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml index c5247dc21ec63..3fd1eacbf3504 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml @@ -30,8 +30,8 @@ <argument name="image" value="ImageUpload"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="clickViewDetails"/> - <actionGroup ref="AdminEnhancedMediaGalleryVerifyUploadedImageDateTimeEqualsActionGroup" stepKey="verifyCreatedAndUpdatedAtDate" /> - <wait time="10" stepKey="waitForUpdateTimeToBeGreater"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup" stepKey="verifyCreatedAndUpdatedAtDate" /> + <wait time="20" stepKey="waitForUpdateTimeToBeGreater"/> <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeViewDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryEditImageDetailsActionGroup" stepKey="editImageDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> @@ -44,7 +44,7 @@ <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDetailsActionGroup" stepKey="verifyImageDetails"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> - <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup" stepKey="assertUpdatedAtTimeChanged" /> + <actionGroup ref="AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup" stepKey="assertUpdatedAtTimeChanged" /> <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDescriptionActionGroup" stepKey="verifyImageDescription"> <argument name="description" value="UpdatedImageDetails.description"/> </actionGroup> From fe0081707fcede404ca852e51f99d4b2018c72fa Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 11 Aug 2020 10:25:30 +0300 Subject: [PATCH 23/57] Rename file --- ...diaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/{AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml => AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml} (100%) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml similarity index 100% rename from app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml rename to app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml From d4e27fbcd7aff610ff2ce0735944c1b21b54bd31 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 11 Aug 2020 10:34:12 +0300 Subject: [PATCH 24/57] Fix flaky bahavior --- .../Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml index 3fd1eacbf3504..58c6f32b8d72f 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml @@ -31,7 +31,7 @@ </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="clickViewDetails"/> <actionGroup ref="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup" stepKey="verifyCreatedAndUpdatedAtDate" /> - <wait time="20" stepKey="waitForUpdateTimeToBeGreater"/> + <wait time="100" stepKey="waitForUpdateTimeToBeGreater"/> <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeViewDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryEditImageDetailsActionGroup" stepKey="editImageDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> From 7e34bc97092166109f2fbadda8d187a86ca7105e Mon Sep 17 00:00:00 2001 From: yolouiese <honeymay@abovethefray.io> Date: Tue, 11 Aug 2020 21:19:45 +0800 Subject: [PATCH 25/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - covered MFTF test --- .../AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml index 3672f582b0877..db59369638da9 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -5,7 +5,6 @@ * See COPYING.txt for license details. */ --> - <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminEnhancedMediaGalleryVerifyUpdatedTagsTest"> <annotations> @@ -26,8 +25,9 @@ <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsDeleteActionGroup" stepKey="deleteImage"/> </after> <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> - <argument name="image" value="ImageUpload"/> + <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"/> @@ -35,6 +35,12 @@ <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> + <actionGroup ref="AssertImageAttributesOnEnhancedMediaGalleryActionGroup" stepKey="verifyUpdateImageOnTheGrid"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDetailsActionGroup" stepKey="verifyImageDetails"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyAddedKeywords"> <argument name="keywords" value="UpdatedImageDetails.keyword"/> </actionGroup> From 8b8e496fa243b7fc3f92906cbbfbc1505298adf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Kos?= <kosrafal@gmail.com> Date: Tue, 11 Aug 2020 18:31:12 +0200 Subject: [PATCH 26/57] fix fatal error when exception was thrown --- .../Framework/Stdlib/DateTime/Timezone.php | 2 +- .../Test/Unit/DateTime/TimezoneTest.php | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) 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 * From 0f9189efd2f6b7341c90225d577966f875dbbd83 Mon Sep 17 00:00:00 2001 From: yolouiese <honeymay@abovethefray.io> Date: Wed, 12 Aug 2020 01:06:45 +0800 Subject: [PATCH 27/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - covered MFTF test --- .../Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml | 6 ------ .../view/adminhtml/web/js/image/image-actions.js | 2 +- .../view/adminhtml/web/js/image/image-edit.js | 5 ++--- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml index db59369638da9..4abb819bed215 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -35,12 +35,6 @@ <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> - <actionGroup ref="AssertImageAttributesOnEnhancedMediaGalleryActionGroup" stepKey="verifyUpdateImageOnTheGrid"> - <argument name="image" value="UpdatedImageDetails"/> - </actionGroup> - <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDetailsActionGroup" stepKey="verifyImageDetails"> - <argument name="image" value="UpdatedImageDetails"/> - </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyAddedKeywords"> <argument name="keywords" value="UpdatedImageDetails.keyword"/> </actionGroup> 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 9eea75e8cab91..977ffcd0255ff 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 @@ -99,7 +99,7 @@ define([ this.closeModal(); this.imageModel().reloadGrid(); imageDetails.removeCached(imageId); - imageEditDetails.removeCached(imageId, keywords); + 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 e142fb12aa96a..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 @@ -229,10 +229,9 @@ define([ * Remove cached image details in edit form * * @param {String} id - * @param {String} tags */ - removeCached: function (id, tags) { - this.images[id].tags = tags; + removeCached: function (id) { + delete this.images[id]; } }); }); From 9f02257226d4ac946c572ff4d23ce88ef5265fae Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 01:37:46 +0800 Subject: [PATCH 28/57] magento/adobe-stock-integration#1727: Introduce internal class wrapping SplFileInfo - fix static and mftf fails, added integration tets --- .../Model/Filesystem/FileInfo.php | 55 +---------- .../Model/Filesystem/GetFileInfo.php | 6 +- .../Model/Filesystem/GetFileInfoTest.php | 94 +++++++++++++++++++ 3 files changed, 101 insertions(+), 54 deletions(-) create mode 100644 app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php index 20acefe4ab034..034ae7c0bff5a 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php @@ -8,7 +8,9 @@ namespace Magento\MediaGallerySynchronization\Model\Filesystem; /** - * Class FileInfo + * Internal class wrapping \SplFileInfo + * + * @SuppressWarnings(PHPMD.TooManyFields) */ class FileInfo extends \SplFileInfo { @@ -37,11 +39,6 @@ class FileInfo extends \SplFileInfo */ private $pathname; - /** - * @var int - */ - private $perms; - /** * @var int */ @@ -87,16 +84,6 @@ class FileInfo extends \SplFileInfo */ private $realPath; - /** - * @var \SplFileInfo - */ - private $fileInfo; - - /** - * @var \SplFileInfo - */ - private $pathInfo; - /** * FileInfo constructor. * @param string $file_name @@ -105,7 +92,6 @@ class FileInfo extends \SplFileInfo * @param string $extension * @param string $basename * @param string $pathname - * @param int $perms * @param int $inode * @param int $size * @param int $owner @@ -115,8 +101,7 @@ class FileInfo extends \SplFileInfo * @param int $cTime * @param string $type * @param false|string $realPath - * @param \SplFileInfo $fileInfo - * @param \SplFileInfo $pathInfo + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( string $file_name, @@ -125,7 +110,6 @@ public function __construct( string $extension, string $basename, string $pathname, - int $perms, int $inode, int $size, int $owner, @@ -134,9 +118,7 @@ public function __construct( int $mTime, int $cTime, string $type, - $realPath, - \SplFileInfo $fileInfo, - \SplFileInfo $pathInfo + $realPath ) { parent::__construct($file_name); $this->path = $path; @@ -144,7 +126,6 @@ public function __construct( $this->extension = $extension; $this->basename = $basename; $this->pathname = $pathname; - $this->perms = $perms; $this->inode = $inode; $this->size = $size; $this->owner = $owner; @@ -154,8 +135,6 @@ public function __construct( $this->cTime = $cTime; $this->type = $type; $this->realPath = $realPath; - $this->fileInfo = $fileInfo; - $this->pathInfo = $pathInfo; } /** @@ -198,14 +177,6 @@ public function getPathname(): string return $this->pathname; } - /** - * @inheritDoc - */ - public function getPerms(): int - { - return $this->perms; - } - /** * @inheritDoc */ @@ -277,20 +248,4 @@ public function getRealPath() { return $this->realPath; } - - /** - * @inheritDoc - */ - public function getFileInfo($class_name = null): \SplFileInfo - { - return $this->fileInfo; - } - - /** - * @inheritDoc - */ - public function getPathInfo($class_name = null): \SplFileInfo - { - return $this->pathInfo; - } } diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php index ef382732275bd..e2ffa39d0aba9 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php @@ -44,7 +44,7 @@ public function execute(string $path): FileInfo 'path' => $splFileInfo->getPath(), 'filename' => $splFileInfo->getFilename(), 'extension' => $splFileInfo->getExtension(), - 'basename' => $splFileInfo->getBasename(), + 'basename' => $splFileInfo->getBasename('.' . $splFileInfo->getExtension()), 'pathname' => $splFileInfo->getPathname(), 'perms' => $splFileInfo->getPerms(), 'inode' => $splFileInfo->getInode(), @@ -55,9 +55,7 @@ public function execute(string $path): FileInfo 'mTime' => $splFileInfo->getMTime(), 'cTime' => $splFileInfo->getCTime(), 'type' => $splFileInfo->getType(), - 'realPath' => $splFileInfo->getRealPath(), - 'fileInfo' => $splFileInfo->getFileInfo(), - 'pathInfo' => $splFileInfo->getPathInfo() + 'realPath' => $splFileInfo->getRealPath() ]); } } 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..c88a6fe7d39d7 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Test\Integration\Model\Filesystem; + +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Integration test for GetFileInfo + */ +class GetFileInfoTest extends TestCase +{ + /** + * @var GetFileInfo + */ + private $getFileInfo; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->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); + $this->assertNotEmpty($fileInfo->getPath()); + $this->assertNotEmpty($fileInfo->getFilename()); + $this->assertNotEmpty($fileInfo->getExtension()); + $this->assertNotEmpty($fileInfo->getBasename()); + $this->assertNotEmpty($fileInfo->getPathname()); + $this->assertNotEmpty($fileInfo->getPerms()); + $this->assertNotEmpty($fileInfo->getInode()); + $this->assertNotEmpty($fileInfo->getSize()); + $this->assertNotEmpty($fileInfo->getOwner()); + $this->assertNotEmpty($fileInfo->getGroup()); + $this->assertNotEmpty($fileInfo->getATime()); + $this->assertNotEmpty($fileInfo->getMTime()); + $this->assertNotEmpty($fileInfo->getCTime()); + $this->assertNotEmpty($fileInfo->getType()); + $this->assertNotEmpty($fileInfo->getRealPath()); + + } + + /** + * 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 + ] + ); + } +} From 0b6ba916edc17c9e6a231b7fb3598086c1c365c8 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 03:35:25 +0800 Subject: [PATCH 29/57] magento/adobe-stock-integration#1727: Introduce internal class wrapping SplFileInfo - fix static test --- .../Test/Integration/Model/Filesystem/GetFileInfoTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php index c88a6fe7d39d7..4031de4226105 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -55,7 +55,6 @@ public function testExecute( $this->assertNotEmpty($fileInfo->getCTime()); $this->assertNotEmpty($fileInfo->getType()); $this->assertNotEmpty($fileInfo->getRealPath()); - } /** From 3b350fd86cfa89a147a736d96e7c59ab2ac40b68 Mon Sep 17 00:00:00 2001 From: yolouiese <honeymay@abovethefray.io> Date: Wed, 12 Aug 2020 18:10:46 +0800 Subject: [PATCH 30/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - added verify removal of keyword MFTF test --- ...yVerifyRemovedImageKeywordsActionGroup.xml | 25 +++++++++++++++++++ ...lleryEditAssetRemoveKeywordActionGroup.xml | 21 ++++++++++++++++ ...EnhancedMediaGalleryEditDetailsSection.xml | 1 + ...ancedMediaGalleryVerifyUpdatedTagsTest.xml | 11 ++++++-- 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml new file mode 100644 index 0000000000000..b94fbf369ef01 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup"> + <annotations> + <description>Verifies removed image keywords on the View Details panel</description> + </annotations> + <arguments> + <argument name="keywords"/> + </arguments> + + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.keywords}}" stepKey="grabKeywords"/> + <assertStringNotContainsString stepKey="verifyKeywords"> + <actualResult type="variable">grabKeywords</actualResult> + <expectedResult type="string">{{keywords}}</expectedResult> + </assertStringNotContainsString> + </actionGroup> +</actionGroups> 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..7bba4fd637189 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminMediaGalleryEditAssetRemoveKeywordActionGroup"> + <annotations> + <description>Remove Keywords on the Edit Details panel</description> + </annotations> + <arguments> + <argument name="selectedKeywords"/> + </arguments> + + <click selector="{{AdminEnhancedMediaGalleryEditDetailsSection.removeSelectedKeyword(selectedKeywords)}}" stepKey="removeKeyword"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml index b8e2f698ccfe8..7456db0b4d988 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 @@ <element name="description" type="textarea" selector="#description"/> <element name="newKeyword" type="input" selector="[data-ui-id='keyword']"/> <element name="addNewKeyword" type="input" selector="[data-ui-id='add-keyword']"/> + <element name="removeSelectedKeyword" type="button" selector="//span[contains(text(), '{{selectedKeywords}}')]/following-sibling::button[@data-action='remove-selected-item']" parameterized="true"/> <element name="cancel" type="button" selector="#image-details-action-cancel"/> <element name="save" type="button" selector="#image-details-action-save"/> </section> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml index 4abb819bed215..9571d49a88130 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -38,8 +38,15 @@ <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyAddedKeywords"> <argument name="keywords" value="UpdatedImageDetails.keyword"/> </actionGroup> - <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyMetadataKeywords"> - <argument name="keywords" value="ImageMetadata.keywords"/> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="updateImageDetails"/> + <actionGroup ref="AdminMediaGalleryEditAssetRemoveKeywordActionGroup" stepKey="removeKeywords"> + <argument name="selectedKeywords" value="UpdatedImageDetails.keyword"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveUpdatedImage"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup" stepKey="verifyRemovedKeywords"> + <argument name="keywords" value="UpdatedImageDetails.keyword"/> </actionGroup> </test> </tests> From 68b35c53c6104121095f6ca89cbe3ddd7e9319ce Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 19:58:22 +0800 Subject: [PATCH 31/57] magento/adobe-stock-integration#1712: Remove DataObject usage from OpenDialogUrl provider - remove data object usage from OpenDialogUrl provider, added new plugin and modified the integration test --- .../Plugin/NewMediaGalleryOpenDialogUrl.php | 43 +++++++++++++++++++ ...ProviderTest.php => OpenDialogUrlTest.php} | 16 +++---- .../etc/adminhtml/di.xml | 4 +- .../Element/DataType/Media/OpenDialogUrl.php | 14 +----- 4 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php rename app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/{OpenDialogUrlProviderTest.php => OpenDialogUrlTest.php} (80%) diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php new file mode 100644 index 0000000000000..f076aa43b6f96 --- /dev/null +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryIntegration\Plugin; + +use Magento\MediaGalleryUiApi\Api\ConfigInterface; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; + +class NewMediaGalleryOpenDialogUrl +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @param ConfigInterface $config + */ + public function __construct(ConfigInterface $config) + { + $this->config = $config; + } + + /** + * @param OpenDialogUrl $subject + * @param string $result + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @return string + */ + public function afterGet(OpenDialogUrl $subject, string $result) + { + $writer = new \Zend\Log\Writer\Stream(BP . '/var/log/newmediagalleryplugin.log'); + $logger = new \Zend\Log\Logger(); + $logger->addWriter($writer); + $logger->debug(__METHOD__); + $logger->debug("PASSING HERE!"); + 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/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 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl"> - <arguments> - <argument name="url" xsi:type="object">Magento\MediaGalleryIntegration\Model\OpenDialogUrlProvider</argument> - </arguments> + <plugin name="new_media_gallery_open_dialog_url" type="Magento\MediaGalleryIntegration\Plugin\NewMediaGalleryOpenDialogUrl" /> </type> <type name="Magento\Framework\File\Uploader"> <plugin name="save_asset_image" type="Magento\MediaGalleryIntegration\Plugin\SaveImageInformation"/> 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..cd116a6f1bff8 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 { @@ -22,14 +20,6 @@ class OpenDialogUrl */ private $openDialogUrl; - /** - * @param DataObject $url - */ - public function __construct(DataObject $url = null) - { - $this->openDialogUrl = $url; - } - /** * Returns open dialog url for media browser * @@ -38,7 +28,7 @@ public function __construct(DataObject $url = null) public function get(): string { if ($this->openDialogUrl) { - return $this->openDialogUrl->getUrl(); + return $this->openDialogUrl; } return self::DEFAULT_OPEN_DIALOG_URL; } From 7f115fdf42003bd86ee35336e4f4ee85c54de5c9 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 20:02:43 +0800 Subject: [PATCH 32/57] magento/adobe-stock-integration#1712: Remove DataObject usage from OpenDialogUrl provider - remove logs --- .../Plugin/NewMediaGalleryOpenDialogUrl.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php index f076aa43b6f96..13068721634e8 100644 --- a/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php @@ -33,11 +33,6 @@ public function __construct(ConfigInterface $config) */ public function afterGet(OpenDialogUrl $subject, string $result) { - $writer = new \Zend\Log\Writer\Stream(BP . '/var/log/newmediagalleryplugin.log'); - $logger = new \Zend\Log\Logger(); - $logger->addWriter($writer); - $logger->debug(__METHOD__); - $logger->debug("PASSING HERE!"); return $this->config->isEnabled() ? 'media_gallery/index/index' : $result; } } From 289af76d3f469af88be7734376f325c23ce6a025 Mon Sep 17 00:00:00 2001 From: jekabs <jekabs@developers-alliance.com> Date: Wed, 12 Aug 2020 16:31:14 +0300 Subject: [PATCH 33/57] magento/web-api-test-recursive-array-comparison -Added a function to TestFramework for comparing two arrays recursively. --- .../Helper/CompareArraysRecursively.php | 70 +++++++++++++++++++ .../TestFramework/TestCase/WebapiAbstract.php | 56 --------------- .../Catalog/Api/CategoryManagementTest.php | 17 ++++- 3 files changed, 86 insertions(+), 57 deletions(-) create mode 100644 dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php 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/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php index 91be839aa56cb..7ccab097d7778 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php @@ -766,60 +766,4 @@ protected function assertWebApiCallErrors(array $serviceInfo, array $data, array } } } - - /** - * 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 compareArraysRecursively(array $expected, array $actual): array - { - $diffResult = []; - - foreach ($expected as $key => $value) { - if (array_key_exists($key, $actual)) { - if (is_array($value)) { - $recursiveDiff = $this->compareArraysRecursively($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 965b920319994..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,7 +55,7 @@ public function testTree($rootCategoryId, $depth, $expected) ] ]; $result = $this->_webApiCall($serviceInfo, $requestData); - $diff = $this->compareArraysRecursively($expected, $result); + $diff = $this->compareArraysRecursively->execute($expected, $result); self::assertEquals([], $diff, "Actual categories response doesn't equal expected data"); } From 7ae8c6ee7ea476ec7570438dbc8c80c36932d373 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 21:49:45 +0800 Subject: [PATCH 34/57] magento/adobe-stock-integration#1748: Some category grid columns are empty - fixed issue of empty columns and added column renderer for in menu and enabled columns --- .../Ui/Component/Listing/Columns/InMenu.php | 36 +++++++++++++++++++ .../Ui/Component/Listing/Columns/IsActive.php | 36 +++++++++++++++++++ .../media_gallery_category_listing.xml | 4 +-- 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php 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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; + +use Magento\Ui\Component\Listing\Columns\Column; + +/** + * Class InMenu column for Category grid + */ +class InMenu extends Column +{ + /** + * Prepare data source. + * + * @param array $dataSource + * @return array + */ + 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] == 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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; + +use Magento\Ui\Component\Listing\Columns\Column; + +/** + * Class IsActive column for Category grid + */ +class IsActive extends Column +{ + /** + * Prepare data source. + * + * @param array $dataSource + * @return array + */ + 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] == 1) { + $item[$fieldName] = 'Yes'; + } else { + $item[$fieldName] = 'No'; + } + } + } + + return $dataSource; + } +} 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 @@ <label translate="true">Products</label> </settings> </column> - <column name="include_in_menu" component="Magento_Ui/js/grid/columns/select"> + <column name="include_in_menu" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\InMenu"> <settings> <label translate="true">In Menu</label> </settings> </column> - <column name="is_active" component="Magento_Ui/js/grid/columns/select" > + <column name="is_active" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\IsActive"> <settings> <label translate="true">Enabled</label> </settings> From 0192ca576be48357640029f10edf35e829f2318b Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 12 Aug 2020 22:13:59 +0800 Subject: [PATCH 35/57] magento/adobe-stock-integration#1712: Remove DataObject usage from OpenDialogUrl provider - fixed static test fails and removed unused class --- .../Model/OpenDialogUrlProvider.php | 40 ------------------- .../Plugin/NewMediaGalleryOpenDialogUrl.php | 5 +++ .../MediaGalleryIntegration/composer.json | 3 +- 3 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php diff --git a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php b/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php deleted file mode 100644 index 317b811df5692..0000000000000 --- a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGalleryIntegration\Model; - -use Magento\Framework\DataObject; -use Magento\MediaGalleryUiApi\Api\ConfigInterface; - -/** - * Provider to get open media gallery dialog URL for WYSIWYG and widgets - */ -class OpenDialogUrlProvider extends DataObject -{ - /** - * @var ConfigInterface - */ - private $config; - - /** - * @param ConfigInterface $config - */ - public function __construct(ConfigInterface $config) - { - $this->config = $config; - } - - /** - * Get Url based on media gallery configuration - * - * @return string - */ - public function getUrl(): string - { - return $this->config->isEnabled() ? 'media_gallery/index/index' : 'cms/wysiwyg_images/index'; - } -} diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php index 13068721634e8..ed8108f012af0 100644 --- a/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php @@ -10,6 +10,9 @@ use Magento\MediaGalleryUiApi\Api\ConfigInterface; use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; +/** + * Plugin to get open media gallery dialog URL for WYSIWYG and widgets + */ class NewMediaGalleryOpenDialogUrl { /** @@ -26,6 +29,8 @@ public function __construct(ConfigInterface $config) } /** + * Get Url based on media gallery configuration + * * @param OpenDialogUrl $subject * @param string $result * @SuppressWarnings(PHPMD.UnusedFormalParameter) 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": "*" From a74d422b30b2142d4b2195aa86d58acf0c506bfe Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Wed, 12 Aug 2020 19:24:25 +0300 Subject: [PATCH 36/57] Exif Reader IMplementation --- .../Model/Jpeg/Segment/ReadExif.php | 74 ++++++++++++++++++ .../Test/_files/exif-image.jpeg | Bin 0 -> 22905 bytes .../Magento/MediaGalleryMetadata/etc/di.xml | 1 + 3 files changed, 75 insertions(+) create mode 100644 app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php create mode 100644 app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg 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..0c142403affd1 --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model\Jpeg\Segment; + +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Model\FileInterface; +use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; +use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; + +/** + * Jpeg EXIF Reader + */ +class ReadExif implements ReadMetadataInterface +{ + private const EXIF_SEGMENT_NAME = 'APP1'; + private const EXIF_SEGMENT_START = "Exif\x00"; + private const EXIF_DATA_START_POSITION = 0; + + /** + * @var MetadataInterfaceFactory + */ + private $metadataFactory; + + /** + * @param MetadataInterfaceFactory $metadataFactory + */ + public function __construct( + MetadataInterfaceFactory $metadataFactory + ) { + $this->metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + $title = null; + $description = null; + $keywords = []; + + foreach ($file->getSegments() as $segment) { + if ($this->isExifSegment($segment)) { + } + } + 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 $segment->getName() === self::EXIF_SEGMENT_NAME + && strncmp( + substr($segment->getData(), self::EXIF_DATA_START_POSITION, 4), + self::EXIF_SEGMENT_START, + self::EXIF_DATA_START_POSITION + ) == 0; + } +} 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 0000000000000000000000000000000000000000..7c1dbc3a6a465fa7f9026e088f623387de657a4c GIT binary patch literal 22905 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 U|8Nkv2VWR5Bd@z1^lrTg0QL`_O#lD@ literal 0 HcmV?d00001 diff --git a/app/code/Magento/MediaGalleryMetadata/etc/di.xml b/app/code/Magento/MediaGalleryMetadata/etc/di.xml index d2f1f90510488..d6b2899729fb7 100644 --- a/app/code/Magento/MediaGalleryMetadata/etc/di.xml +++ b/app/code/Magento/MediaGalleryMetadata/etc/di.xml @@ -121,6 +121,7 @@ <argument name="segmentReaders" xsi:type="array"> <item name="xmp" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadXmp</item> <item name="iptc" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadIptc</item> + <item name="exif" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadExif</item> </argument> </arguments> </virtualType> From 23b5a2488586c0ed493a834a04669e5c410ca85a Mon Sep 17 00:00:00 2001 From: Shankar Konar <konar.shankar2013@gmail.com> Date: Thu, 13 Aug 2020 11:22:17 +0530 Subject: [PATCH 37/57] Style changes --- lib/internal/Magento/Framework/Data/Form/Element/Time.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 89e7f22c2d86f2d281749774761d6dc6418e891b Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 13 Aug 2020 18:47:51 +0800 Subject: [PATCH 38/57] magento/adobe-stock-integration#1727: Introduce internal class wrapping SplFileInfo - apply requested changes --- .../Model/CreateAssetFromFile.php | 2 +- .../Model/Filesystem/FileInfo.php | 155 +++--------------- .../Model/Filesystem/GetFileInfo.php | 11 +- .../Model/Filesystem/GetFileInfoTest.php | 23 +-- 4 files changed, 36 insertions(+), 155 deletions(-) diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 6f1f05a750085..b257c3da55c84 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -110,7 +110,7 @@ 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), diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php index 034ae7c0bff5a..5e523fd0e905a 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php @@ -8,11 +8,9 @@ namespace Magento\MediaGallerySynchronization\Model\Filesystem; /** - * Internal class wrapping \SplFileInfo - * - * @SuppressWarnings(PHPMD.TooManyFields) + * Class for getting image file information. */ -class FileInfo extends \SplFileInfo +class FileInfo { /** * @var string @@ -34,36 +32,11 @@ class FileInfo extends \SplFileInfo */ private $basename; - /** - * @var string - */ - private $pathname; - - /** - * @var int - */ - private $inode; - /** * @var int */ private $size; - /** - * @var int - */ - private $owner; - - /** - * @var int - */ - private $group; - - /** - * @var int - */ - private $aTime; - /** * @var int */ @@ -74,71 +47,39 @@ class FileInfo extends \SplFileInfo */ private $cTime; - /** - * @var string - */ - private $type; - - /** - * @var false|string - */ - private $realPath; - /** * FileInfo constructor. - * @param string $file_name + * * @param string $path * @param string $filename * @param string $extension * @param string $basename - * @param string $pathname - * @param int $inode * @param int $size - * @param int $owner - * @param int $group - * @param int $aTime * @param int $mTime * @param int $cTime - * @param string $type - * @param false|string $realPath - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - string $file_name, string $path, string $filename, string $extension, string $basename, - string $pathname, - int $inode, int $size, - int $owner, - int $group, - int $aTime, int $mTime, - int $cTime, - string $type, - $realPath + int $cTime ) { - parent::__construct($file_name); $this->path = $path; $this->filename = $filename; $this->extension = $extension; $this->basename = $basename; - $this->pathname = $pathname; - $this->inode = $inode; $this->size = $size; - $this->owner = $owner; - $this->group = $group; - $this->aTime = $aTime; $this->mTime = $mTime; $this->cTime = $cTime; - $this->type = $type; - $this->realPath = $realPath; } /** - * @inheritDoc + * Get path without filename. + * + * @return string */ public function getPath(): string { @@ -146,7 +87,9 @@ public function getPath(): string } /** - * @inheritDoc + * Get filename. + * + * @return string */ public function getFilename(): string { @@ -154,7 +97,9 @@ public function getFilename(): string } /** - * @inheritDoc + * Get file extension. + * + * @return string */ public function getExtension(): string { @@ -162,31 +107,19 @@ public function getExtension(): string } /** - * @inheritDoc + * Get file basename. + * + * @return string */ - public function getBasename($suffix = null): string + public function getBasename(): string { return $this->basename; } /** - * @inheritDoc - */ - public function getPathname(): string - { - return $this->pathname; - } - - /** - * @inheritDoc - */ - public function getInode(): int - { - return $this->inode; - } - - /** - * @inheritDoc + * Get file size. + * + * @return int */ public function getSize(): int { @@ -194,31 +127,9 @@ public function getSize(): int } /** - * @inheritDoc - */ - public function getOwner(): int - { - return $this->owner; - } - - /** - * @inheritDoc - */ - public function getGroup(): int - { - return $this->group; - } - - /** - * @inheritDoc - */ - public function getATime(): int - { - return $this->aTime; - } - - /** - * @inheritDoc + * Get last modified time. + * + * @return int */ public function getMTime(): int { @@ -226,26 +137,12 @@ public function getMTime(): int } /** - * @inheritDoc + * Get inode change time. + * + * @return int */ public function getCTime(): int { return $this->cTime; } - - /** - * @inheritDoc - */ - public function getType(): string - { - return $this->type; - } - - /** - * @inheritDoc - */ - public function getRealPath() - { - return $this->realPath; - } } diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php index e2ffa39d0aba9..8f9080767d6e3 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php @@ -40,22 +40,13 @@ public function execute(string $path): FileInfo $splFileInfo = new \SplFileInfo($path); return $this->fileInfoFactory->create([ - 'file_name' => $path, 'path' => $splFileInfo->getPath(), 'filename' => $splFileInfo->getFilename(), 'extension' => $splFileInfo->getExtension(), 'basename' => $splFileInfo->getBasename('.' . $splFileInfo->getExtension()), - 'pathname' => $splFileInfo->getPathname(), - 'perms' => $splFileInfo->getPerms(), - 'inode' => $splFileInfo->getInode(), 'size' => $splFileInfo->getSize(), - 'owner' => $splFileInfo->getOwner(), - 'group' => $splFileInfo->getGroup(), - 'aTime' => $splFileInfo->getATime(), 'mTime' => $splFileInfo->getMTime(), - 'cTime' => $splFileInfo->getCTime(), - 'type' => $splFileInfo->getType(), - 'realPath' => $splFileInfo->getRealPath() + 'cTime' => $splFileInfo->getCTime() ]); } } diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php index 4031de4226105..cc9b70e623d9f 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -40,21 +40,14 @@ public function testExecute( $path = $this->getImageFilePath($file); $fileInfo = $this->getFileInfo->execute($path); - $this->assertNotEmpty($fileInfo->getPath()); - $this->assertNotEmpty($fileInfo->getFilename()); - $this->assertNotEmpty($fileInfo->getExtension()); - $this->assertNotEmpty($fileInfo->getBasename()); - $this->assertNotEmpty($fileInfo->getPathname()); - $this->assertNotEmpty($fileInfo->getPerms()); - $this->assertNotEmpty($fileInfo->getInode()); - $this->assertNotEmpty($fileInfo->getSize()); - $this->assertNotEmpty($fileInfo->getOwner()); - $this->assertNotEmpty($fileInfo->getGroup()); - $this->assertNotEmpty($fileInfo->getATime()); - $this->assertNotEmpty($fileInfo->getMTime()); - $this->assertNotEmpty($fileInfo->getCTime()); - $this->assertNotEmpty($fileInfo->getType()); - $this->assertNotEmpty($fileInfo->getRealPath()); + $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()); } /** From ded3e1ebc95df3f9aafcead5dfff395ed85c17bf Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 13 Aug 2020 19:40:27 +0800 Subject: [PATCH 39/57] magento/adobe-stock-integration#1748: Some category grid columns are empty - added columns to section and added verification to action group --- .../AdminAssertCategoryGridPageDetailsActionGroup.xml | 2 ++ .../Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml index 0788bbd60291a..7875c62f9591d 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml @@ -16,5 +16,7 @@ <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name('1', 'Default Category')}}" stepKey="assertNameColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.displayMode('1', 'PRODUCTS')}}" stepKey="assertDisplayModeColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('1', '0')}}" stepKey="assertProductsColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.inMenu('1', 'Yes')}}" stepKey="assertInMenuColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.enabled('1', 'Yes')}}" stepKey="assertEnabledColumn"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 5267a215c8edd..41bec6f6220a0 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -13,6 +13,8 @@ <element name="name" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Name')]/preceding-sibling::th) +1 ]//*[text()='{{categoryName}}']" parameterized="true"/> <element name="displayMode" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Display Mode')]/preceding-sibling::th) +1 ]//*[text()='{{productsText}}']" parameterized="true"/> <element name="products" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Products')]/preceding-sibling::th) +1 ]//*[text()='{{productsQty}}']" parameterized="true"/> + <element name="inMenu" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'In Menu')]/preceding-sibling::th) +1 ]//*[text()='{{inMenuValue}}']" parameterized="true"/> + <element name="enabled" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Enabled')]/preceding-sibling::th) +1 ]//*[text()='{{enabledValue}}']" parameterized="true"/> <element name="edit" type="button" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{edit}}']" parameterized="true"/> </section> </sections> From 9b8c81c657102c5b7e2fee8fc91ae926c82e2c67 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 13 Aug 2020 14:49:16 +0300 Subject: [PATCH 40/57] add integration test for exif standart --- .../Model/Jpeg/Segment/ExifTest.php | 132 ++++++++++++++++++ .../Test/_files/exif-image.jpeg | Bin 22905 -> 19494 bytes 2 files changed, 132 insertions(+) create mode 100644 app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php new file mode 100644 index 0000000000000..93c4ced52bc9f --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Test\Integration\Model\Jpeg\Segment; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; +use Magento\MediaGalleryMetadata\Model\Jpeg\ReadFile; +use Magento\MediaGalleryMetadata\Model\MetadataFactory; + +/** + * Test for EXIF reader + */ +class ExifTest extends TestCase +{ + /** + * @var WriteIptc + */ + private $iptcWriter; + + /** + * @var ReadIptc + */ + private $iptcReader; + + /** + * @var DriverInterface + */ + private $driver; + + /** + * @var ReadFile + */ + private $fileReader; + + /** + * @var MetadataFactory + */ + private $metadataFactory; + + /** + * @var WriteInterface + */ + private $varDirectory; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) + ->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->iptcWriter = Bootstrap::getObjectManager()->get(WriteIptc::class); + $this->iptcReader = Bootstrap::getObjectManager()->get(ReadIptc::class); + $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); + $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); + } + + /** + * Test for IPTC reader and writer + * + * @dataProvider filesProvider + * @param string $fileName + * @param string $title + * @param string $description + * @param array $keywords + * @throws LocalizedException + */ + public function testWriteRead( + string $fileName, + string $title, + string $description, + array $keywords + ): void { + $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); + $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); + $this->driver->copy( + $path, + $modifiableFilePath + ); + $modifiableFilePath = $this->fileReader->execute($modifiableFilePath); + $originalMetadata = $this->iptcReader->execute($modifiableFilePath); + + $this->assertEmpty($originalMetadata->getTitle()); + $this->assertEmpty($originalMetadata->getDescription()); + $this->assertEmpty($originalMetadata->getKeywords()); + + $updatedFile = $this->iptcWriter->execute( + $modifiableFilePath, + $this->metadataFactory->create([ + 'title' => $title, + 'description' => $description, + 'keywords' => $keywords + ]) + ); + + $updatedMetadata = $this->iptcReader->execute($updatedFile); + + $this->assertEquals($title, $updatedMetadata->getTitle()); + $this->assertEquals($description, $updatedMetadata->getDescription()); + $this->assertEquals($keywords, $updatedMetadata->getKeywords()); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function filesProvider(): array + { + return [ + [ + 'empty_iptc.jpeg', + 'Updated Title', + 'Updated Description', + [ + 'magento2', + 'mediagallery' + ] + ] + ]; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg index 7c1dbc3a6a465fa7f9026e088f623387de657a4c..666488a923b2cbe4e2b1150e27da140b2016b29c 100644 GIT binary patch delta 156 zcmeyliE-Hs#`^y^{y$`x<64oK#=zj~%b>-;z`()4#>mUS1Y`*VaTp^Xm@NflXE91K zumWjDAZ}rlhO>JZH5iz|;y_(Y!caCy6%c^TR`5+sPt7aIS18FW$w`HZrKA=oPuwcP R>j6{*QVXUxf97rT1^{$f8%F>D delta 3610 zcmeHK&2Q936n{>F_!1wDdV(A*BLpg}?RCOOtT+e~0x4QRBvPy19DBU$#%mjU!a95L z4K9do_kcJc6bUKf2t6V7(0_n4Y9$VE<AS(Vs#IzFX4dv@+rV<!+pab8Z)e{8-h032 zXN|WX34i?}q@CY8Z{hH_OUp|j2mmd-pz}_6c{y<%fJz0v0f0TQS2zG;h`z?r6b^EH z1n~o562|c^;03}F*u^oxGvR1~p9?258)Oi)g6HKu7t)Dr!~4fi7zIq>c!dL403vm% zN5ndzrfpKQJ7<&FirkR8f!|xf4H!6Q;CD^cR9!u*YnrH)^%+f{R^c)v;DQ6EVI2r; zLIVOwBd{T6{`R%T^T-6JHBsPZ>no0TBm0BaWPu;H?*h)jB+fr+|IFtn&Y_!jyN9=g zo;q7&kR?0T-Wtzv3h+1czEiwA&7D%qx~c-?_lqkF%geZb#fk{=<61yF@Am&uqcqeN z#gApv4%Ucl1$8AcL!~UM3d~(dLep9&R9qv~)b$PN<FCI-qH7z{cV{YUC0rmi_i8gD zE6wYxR&(8&wWSO5;#{I9^^lc9OzNH=>xm(8b{#RBRwR*|&~-!lzH(Ds2qGfR$TPB9 z!dl+ZB;<%}N58eWlriAH)R3}Z8;yqCn3jX6s+4DEXBAabG_8atrFhe)W>WIwV>!iQ z-y^^%&9z{I8WIN3jjSFPv^#2$8-}$spaH7(3HuQB<jY*Sd>3^cXJ>x4@-I`{Dh$Jo z$m7}BmO?yICq9jpaw?aZZuL-Kh=@taA`U}hl`pAhOPaQtKfPU}N?^Or=4XtGh;?Sv z<;22v2^nWW*nNUh346?O^c#_j0hz3+45`d?wk^G{)3VxK^k{}Wmx--=re8Irq+}Dv z-0-M0-(Q*l8@Pt;YgUmv5M2i56jm_LMH+@uy-TV5&mB(i?*C%XtNdN@1wQ*+(v)pN z7w9eCSo@J!d48A7YLV%DmO)?aSrT<(yJpq&JQ8i@Z>c^jo@<whCD`-87noFO_9n~Z zV9mi`(QEVKaDVA==?Aip{1239s0Jhbhbr$t@{#@nB^s*1NdKYAJCJ;&|3HcUW6(Rk z6CY1@4LqB7KETzR`B7DzmQ^@^X}L1NzBT|3O$k51_|fB6rts@)3@^TRoUa`|cj)}d X?fCY6|MjsGKfU>L`{|3VyVw2!O?J2y From 5c7c95955dc4afe349d284f60d9534b127ae9508 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 13 Aug 2020 14:49:56 +0300 Subject: [PATCH 41/57] skip tests cases --- .../Integration/Model/ExtractMetadataTest.php | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php index 982ccbb20fe2c..a5ff5de0aeaef 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php @@ -63,50 +63,59 @@ public function filesProvider(): array { return [ [ - 'macos-photos.jpeg', + 'exif-image.jpeg', 'Title of the magento image', - 'Description of the magento image', + 'exif fromat title', [ - 'magento', - 'mediagallerymetadata' - ] - ], - [ - 'macos-preview.png', - 'Title of the magento image', - 'Description of the magento image', - [ - 'magento', - 'mediagallerymetadata' - ] - ], - [ - 'iptc_only.jpeg', - 'Title of the magento image', - 'Description of the magento image', - [ - 'magento', - 'mediagallerymetadata' - ] - ], - [ - 'exiftool.gif', - 'Title of the magento image', - 'Description of the magento image', - [ - 'magento', - 'mediagallerymetadata' - ] - ], - [ - 'iptc_only.png', - 'Title of the magento image', - 'PNG format is awesome', - [ - 'png', + 'exif', 'awesome' ] ], + //[ + // 'macos-photos.jpeg', + // 'Title of the magento image', + // 'Description of the magento image', + // [ + // 'magento', + // 'mediagallerymetadata' + // ] + //], + // [ + // 'macos-preview.png', + // 'Title of the magento image', + // 'Description of the magento image', + // [ + // 'magento', + // 'mediagallerymetadata' + /// ] + // ], + // [ + // 'iptc_only.jpeg', + /// 'Title of the magento image', + // 'Description of the magento image', + // [ + // 'magento', + // 'mediagallerymetadata' + // ] + //], + //[ + // 'exiftool.gif', + // 'Title of the magento image', + // 'Description of the magento image', + // [ + // 'magento', + // 'mediagallerymetadata' + // ] + // ], + //[ + // 'iptc_only.png', + // 'Title of the magento image', + // 'PNG format is awesome', + // [ + // 'png', + // 'awesome' + // ] + ///], ]; } } From e2f28054c87bba6cc8457c1f910ad808e32622e5 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 13 Aug 2020 21:29:48 +0800 Subject: [PATCH 42/57] magento/adobe-stock-integration#1712: Remove DataObject usage from OpenDialogUrl provider - apply requested changes --- .../Form/Element/DataType/Media/OpenDialogUrl.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 cd116a6f1bff8..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 @@ -20,6 +20,14 @@ class OpenDialogUrl */ private $openDialogUrl; + /** + * @param string $url + */ + public function __construct(string $url = null) + { + $this->openDialogUrl = $url ?? self::DEFAULT_OPEN_DIALOG_URL; + } + /** * Returns open dialog url for media browser * @@ -27,9 +35,6 @@ class OpenDialogUrl */ public function get(): string { - if ($this->openDialogUrl) { - return $this->openDialogUrl; - } - return self::DEFAULT_OPEN_DIALOG_URL; + return $this->openDialogUrl; } } From 3fc230358a246780d2595b6b2606b7b355af0665 Mon Sep 17 00:00:00 2001 From: Shankar Konar <konar.shankar2013@gmail.com> Date: Fri, 14 Aug 2020 13:42:22 +0530 Subject: [PATCH 43/57] Revert "Media Gallery configuration are reversed" This reverts commit 36335422453af4abf58859acba7224cabe0f5e43. --- .../SaveBaseCategoryImageInformation.php | 2 +- .../Model/OpenDialogUrlProvider.php | 2 +- .../Plugin/SaveImageInformation.php | 2 +- .../Model/ImageComponentOpenDialogUrlTest.php | 4 ++-- .../Model/OpenDialogUrlProviderTest.php | 4 ++-- .../Model/TinyMceOpenDialogUrlTest.php | 4 ++-- .../WysiwygDefaultConfigOpenDialogUrlTest.php | 4 ++-- .../Plugin/MediaGallerySyncTrigger.php | 2 +- .../Test/Mftf/Data/AdobeStockConfigData.xml | 4 ++-- .../MediaGalleryUi/etc/adminhtml/menu.xml | 18 ++---------------- .../MediaGalleryUi/etc/adminhtml/system.xml | 4 ++-- 11 files changed, 18 insertions(+), 32 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php b/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php index 67a99bfaa84c2..d439b53c120cb 100644 --- a/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php +++ b/app/code/Magento/MediaGalleryCatalogIntegration/Plugin/SaveBaseCategoryImageInformation.php @@ -86,7 +86,7 @@ public function __construct( */ public function afterMoveFileFromTmp(ImageUploader $subject, string $imagePath): string { - if ($this->config->isEnabled()) { + if (!$this->config->isEnabled()) { return $imagePath; } diff --git a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php b/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php index 3834550f2703e..317b811df5692 100644 --- a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php +++ b/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php @@ -35,6 +35,6 @@ public function __construct(ConfigInterface $config) */ public function getUrl(): string { - return $this->config->isEnabled() ? 'cms/wysiwyg_images/index' : 'media_gallery/index/index'; + return $this->config->isEnabled() ? 'media_gallery/index/index' : 'cms/wysiwyg_images/index'; } } diff --git a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php index 4d0e6200ddaad..fbe35db298b04 100644 --- a/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/SaveImageInformation.php @@ -78,7 +78,7 @@ public function __construct( */ public function afterSave(Uploader $subject, array $result): array { - if ($this->config->isEnabled()) { + if (!$this->config->isEnabled()) { return $result; } diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php index 0e5cd070a7eec..dfeaa3eff56bd 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/ImageComponentOpenDialogUrlTest.php @@ -50,7 +50,7 @@ protected function setUp(): void /** * Test image open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -61,7 +61,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test image open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php index 3973ed132b490..7a3316f293879 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php @@ -45,7 +45,7 @@ protected function setUp(): void /** * Test getting open dialog url with enhanced media gallery disabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -54,7 +54,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test getting open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php index c15536f6af445..81a4dc642cfa0 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/TinyMceOpenDialogUrlTest.php @@ -58,7 +58,7 @@ protected function setUp(): void /** * Test image open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -68,7 +68,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test image open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php index df6cbe4fc2c09..aebf5927869d5 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/WysiwygDefaultConfigOpenDialogUrlTest.php @@ -58,7 +58,7 @@ protected function setUp(): void /** * Test update wysiwyg editor open dialog url when enhanced media gallery not enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 1 + * @magentoConfigFixture default/system/media_gallery/enabled 0 */ public function testWithEnhancedMediaGalleryDisabled(): void { @@ -70,7 +70,7 @@ public function testWithEnhancedMediaGalleryDisabled(): void /** * Test update wysiwyg editor open dialog url when enhanced media gallery enabled. - * @magentoConfigFixture default/system/media_gallery/enabled 0 + * @magentoConfigFixture default/system/media_gallery/enabled 1 */ public function testWithEnhancedMediaGalleryEnabled(): void { diff --git a/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php b/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php index dfe007fa59add..9583c91184d1a 100644 --- a/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php +++ b/app/code/Magento/MediaGallerySynchronization/Plugin/MediaGallerySyncTrigger.php @@ -43,7 +43,7 @@ public function afterSave(Value $config, Value $result): Value { if ($result->getPath() === self::MEDIA_GALLERY_CONFIG_VALUE && $result->isValueChanged() - && (int) $result->getValue() !== self::MEDIA_GALLERY_ENABLED_VALUE + && (int) $result->getValue() === self::MEDIA_GALLERY_ENABLED_VALUE ) { $this->publish->execute(); } diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml index 5e02d81fb82ae..e8f394a006104 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Data/AdobeStockConfigData.xml @@ -9,10 +9,10 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="MediaGalleryConfigDataEnabled"> <data key="path">system/media_gallery/enabled</data> - <data key="value">0</data> + <data key="value">1</data> </entity> <entity name="MediaGalleryConfigDataDisabled"> <data key="path">system/media_gallery/enabled</data> - <data key="value">1</data> + <data key="value">0</data> </entity> </entities> diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml index e7486ccbf5a2f..92839aa75ac8b 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/menu.xml @@ -7,21 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd"> <menu> - <add id="Magento_MediaGalleryUi::media" - title="Media" - translate="title" - module="Magento_MediaGalleryUi" - sortOrder="15" - parent="Magento_Backend::content" - resource="Magento_Cms::media_gallery" - dependsOnConfig="system/media_gallery/enabled"/> - <add id="Magento_MediaGalleryUi::media_gallery" - title="Media Gallery" - translate="title" - module="Magento_MediaGalleryUi" - sortOrder="0" - parent="Magento_MediaGalleryUi::media" - action="media_gallery/media/index" - resource="Magento_Cms::media_gallery"/> + <add id="Magento_MediaGalleryUi::media" title="Media" translate="title" module="Magento_MediaGalleryUi" sortOrder="15" parent="Magento_Backend::content" resource="Magento_Cms::media_gallery" dependsOnConfig="system/media_gallery/enabled"/> + <add id="Magento_MediaGalleryUi::media_gallery" title="Media Gallery" translate="title" module="Magento_MediaGalleryUi" sortOrder="0" parent="Magento_MediaGalleryUi::media" action="media_gallery/media/index" resource="Magento_Cms::media_gallery"/> </menu> </config> diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml index 1c303ab155dae..77544b42e899a 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml @@ -9,9 +9,9 @@ <system> <section id="system"> <group id="media_gallery" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Media Gallery</label> + <label>Enhanced Media Gallery</label> <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Enable Old Media Gallery</label> + <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>system/media_gallery/enabled</config_path> </field> From e76cb954bd71db1d9d8f9ca5d229b139d9958424 Mon Sep 17 00:00:00 2001 From: Shankar Konar <konar.shankar2013@gmail.com> Date: Fri, 14 Aug 2020 13:45:37 +0530 Subject: [PATCH 44/57] Revert "Enabled old media gallery by default" This reverts commit 8b592ee2005c4c324672b0b3f1f69307384c43c5. --- app/code/Magento/MediaGalleryUi/etc/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryUi/etc/config.xml b/app/code/Magento/MediaGalleryUi/etc/config.xml index 593b1b8e58fd2..fe8e73c406e59 100644 --- a/app/code/Magento/MediaGalleryUi/etc/config.xml +++ b/app/code/Magento/MediaGalleryUi/etc/config.xml @@ -9,7 +9,7 @@ <default> <system> <media_gallery> - <enabled>1</enabled> + <enabled>0</enabled> </media_gallery> </system> </default> From 1fe9c2268eafa22d1318d459297de3041a9425ca Mon Sep 17 00:00:00 2001 From: Shankar Konar <konar.shankar2013@gmail.com> Date: Fri, 14 Aug 2020 13:46:08 +0530 Subject: [PATCH 45/57] Revert "fixed MFTF test" This reverts commit 651bd690cb0687565cc2a1e99a57d99be5e8f14c. --- .../MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml index 14464cf36234c..4749fc4a885b0 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiSuite.xml @@ -13,7 +13,7 @@ <actionGroup ref="AdminDisableWYSIWYGActionGroup" stepKey="disableWYSIWYG" /> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="AdminMediaGalleryEnhancedEnableActionGroup" stepKey="enableEnhancedMediaGallery"> - <argument name="enabled" value="0"/> + <argument name="enabled" value="1"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </before> From de6d2a7ad9ecc1d58fbfd72aafbe8843934c123f Mon Sep 17 00:00:00 2001 From: Shankar Konar <konar.shankar2013@gmail.com> Date: Fri, 14 Aug 2020 13:58:35 +0530 Subject: [PATCH 46/57] Reversed Store configuration --- .../Model/Config/MediaGallery/Yesno.php | 21 +++++++++++++++++++ .../MediaGalleryUi/etc/adminhtml/system.xml | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php 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 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryUi\Model\Config\MediaGallery; + +class Yesno implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * Options getter + * + * @return array + */ + public function toOptionArray() :array + { + return [['value' => 0, 'label' => __('Yes')], ['value' => 1, 'label' => __('No')]]; + } +} 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> From beae26b45c3a175121bd4c0b307b94d513571997 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Fri, 14 Aug 2020 10:16:54 +0100 Subject: [PATCH 47/57] Fixed static test --- .../Test/Integration/Model/Filesystem/GetFileInfoTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php index cc9b70e623d9f..6b1e8a676d02b 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -44,7 +44,10 @@ public function testExecute( $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->getBasename('.' . $expectedResult->getExtension()), + $fileInfo->getBasename() + ); $this->assertEquals($expectedResult->getSize(), $fileInfo->getSize()); $this->assertEquals($expectedResult->getMTime(), $fileInfo->getMTime()); $this->assertEquals($expectedResult->getCTime(), $fileInfo->getCTime()); From 5f1d43a80d6dadbe5b2442c873de94f582f1ea7f Mon Sep 17 00:00:00 2001 From: yolouiese <honeymay@abovethefray.io> Date: Fri, 14 Aug 2020 20:06:50 +0800 Subject: [PATCH 48/57] magento/magento2#1703: The deleted tags are not removed from Tags drop-down until the web page is refreshed - added requested changes --- .../AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml | 4 ++-- ...edMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml} | 6 +++--- .../Section/AdminEnhancedMediaGalleryEditDetailsSection.xml | 2 +- .../Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/{AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml => AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml} (73%) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml index 7bba4fd637189..b2ce726b3bd6c 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml @@ -13,9 +13,9 @@ <description>Remove Keywords on the Edit Details panel</description> </annotations> <arguments> - <argument name="selectedKeywords"/> + <argument name="keyword" type="string"/> </arguments> - <click selector="{{AdminEnhancedMediaGalleryEditDetailsSection.removeSelectedKeyword(selectedKeywords)}}" stepKey="removeKeyword"/> + <click selector="{{AdminEnhancedMediaGalleryEditDetailsSection.removeSelectedKeyword(keyword)}}" stepKey="removeKeyword"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml similarity index 73% rename from app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml rename to app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml index b94fbf369ef01..be9c7e939103d 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml @@ -8,12 +8,12 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup"> + <actionGroup name="AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup"> <annotations> - <description>Verifies removed image keywords on the View Details panel</description> + <description>Verifies that the passed comma-separated list of keywords are not present on the View Details panel</description> </annotations> <arguments> - <argument name="keywords"/> + <argument name="keywords" type="string"/> </arguments> <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.keywords}}" stepKey="grabKeywords"/> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml index 7456db0b4d988..b0bed4563003e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml @@ -14,7 +14,7 @@ <element name="description" type="textarea" selector="#description"/> <element name="newKeyword" type="input" selector="[data-ui-id='keyword']"/> <element name="addNewKeyword" type="input" selector="[data-ui-id='add-keyword']"/> - <element name="removeSelectedKeyword" type="button" selector="//span[contains(text(), '{{selectedKeywords}}')]/following-sibling::button[@data-action='remove-selected-item']" parameterized="true"/> + <element name="removeSelectedKeyword" type="button" selector="//span[contains(text(), '{{keyword}}')]/following-sibling::button[@data-action='remove-selected-item']" parameterized="true"/> <element name="cancel" type="button" selector="#image-details-action-cancel"/> <element name="save" type="button" selector="#image-details-action-save"/> </section> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml index 9571d49a88130..f47d6d9202c05 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -40,13 +40,13 @@ </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="updateImageDetails"/> <actionGroup ref="AdminMediaGalleryEditAssetRemoveKeywordActionGroup" stepKey="removeKeywords"> - <argument name="selectedKeywords" value="UpdatedImageDetails.keyword"/> + <argument name="keyword" value="{{UpdatedImageDetails.keyword}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveUpdatedImage"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> - <actionGroup ref="AdminEnhancedMediaGalleryVerifyRemovedImageKeywordsActionGroup" stepKey="verifyRemovedKeywords"> - <argument name="keywords" value="UpdatedImageDetails.keyword"/> + <actionGroup ref="AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup" stepKey="verifyRemovedKeywords"> + <argument name="keywords" value="{{UpdatedImageDetails.keyword}}"/> </actionGroup> </test> </tests> From 4ef4c6c52d889213e536575d732c9ab00b6806d3 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 14:21:45 +0300 Subject: [PATCH 49/57] Add suport of reading PNG JPEG Exif metadata --- .../Model/Jpeg/Segment/ReadExif.php | 36 ++++++- .../Model/Png/Segment/ReadExif.php | 91 ++++++++++++++++ .../Integration/Model/ExtractMetadataTest.php | 101 +++++++++--------- .../Test/_files/exif-image.jpeg | Bin 19494 -> 19630 bytes .../Test/_files/exif_image.png | Bin 0 -> 47756 bytes .../Magento/MediaGalleryMetadata/etc/di.xml | 1 + 6 files changed, 177 insertions(+), 52 deletions(-) create mode 100644 app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php create mode 100644 app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php index 0c142403affd1..5e5deb0e119fc 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -41,14 +41,44 @@ public function __construct( */ public function execute(FileInterface $file): MetadataInterface { - $title = null; - $description = null; - $keywords = []; + 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 = []; + + $data = exif_read_data($filePath); + + if ($data) { + $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; + $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; + $keywords = ''; + } + return $this->metadataFactory->create([ 'title' => $title, 'description' => $description, 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..cd1160ed92589 --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model\Png\Segment; + +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Model\FileInterface; +use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; +use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; + +/** + * Jpeg EXIF Reader + */ +class ReadExif implements ReadMetadataInterface +{ + private const EXIF_SEGMENT_NAME = 'eXIf'; + + /** + * @var MetadataInterfaceFactory + */ + private $metadataFactory; + + /** + * @param MetadataInterfaceFactory $metadataFactory + */ + public function __construct( + MetadataInterfaceFactory $metadataFactory + ) { + $this->metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + 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 FileInterface $filePath + */ + 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; + $keywords = ''; + } + + 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 $segment->getName() === self::EXIF_SEGMENT_NAME; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php index a5ff5de0aeaef..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,60 +62,63 @@ 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', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ], + [ + 'macos-preview.png', + 'Title of the magento image', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ], + [ + 'iptc_only.jpeg', + 'Title of the magento image', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ], + [ + 'exiftool.gif', + 'Title of the magento image', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ], + [ + 'iptc_only.png', 'Title of the magento image', - 'exif fromat title', + 'PNG format is awesome', [ - 'exif', + 'png', 'awesome' ] ], - //[ - // 'macos-photos.jpeg', - // 'Title of the magento image', - // 'Description of the magento image', - // [ - // 'magento', - // 'mediagallerymetadata' - // ] - //], - // [ - // 'macos-preview.png', - // 'Title of the magento image', - // 'Description of the magento image', - // [ - // 'magento', - // 'mediagallerymetadata' - /// ] - // ], - // [ - // 'iptc_only.jpeg', - /// 'Title of the magento image', - // 'Description of the magento image', - // [ - // 'magento', - // 'mediagallerymetadata' - // ] - //], - //[ - // 'exiftool.gif', - // 'Title of the magento image', - // 'Description of the magento image', - // [ - // 'magento', - // 'mediagallerymetadata' - // ] - // ], - //[ - // 'iptc_only.png', - // 'Title of the magento image', - // 'PNG format is awesome', - // [ - // 'png', - // 'awesome' - // ] - ///], ]; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg index 666488a923b2cbe4e2b1150e27da140b2016b29c..cfe27433fd9fce9d4b0202f604c13a3848f90c35 100644 GIT binary patch delta 269 zcmZ2BgK^zV#`^y^{y$_?ajnQqV_@+0Wzb?^VBlcjVB}?B0<wgGIFFGJ%$5SOn;4}S zSb;Pn5YJ(hhO?J2YA`T^#euq*grRJps`g9<7O47TbLN5dfy`M0#0(%i6(*)z)~7O5 zFk~{MG3YQPGL$o<G88l9GvoqkAYh%qz`zcqSqzMfjV3Ze*o;if43pZxY!RSaI2ORv z7#bKXV1!uy|364O1A}9*v!^GJP6b+~lUSZwoS&Qe{{Vv^2g4txNz9B&3`~L>*Oz+( E02;e1cmMzZ delta 109 zcmZ2ClX2M$#<>4C{y$`x<64oK#=zj~%b>-;z`()4#>mUS1Y`*VaTp^Xm@NflXE91K lumWjDAZ}rlhO>JZH5iz|;y_(Y!caC))x<KV%^Hkt-T+V<4>te+ 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 0000000000000000000000000000000000000000..4a6bf30c2d516ee21c515ccff1fbe812299cd0a8 GIT binary patch literal 47756 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 UKBqk>1m_4!a_W#Onb#rz3wPioP5=M^ literal 0 HcmV?d00001 diff --git a/app/code/Magento/MediaGalleryMetadata/etc/di.xml b/app/code/Magento/MediaGalleryMetadata/etc/di.xml index d6b2899729fb7..4cd9a34e43a93 100644 --- a/app/code/Magento/MediaGalleryMetadata/etc/di.xml +++ b/app/code/Magento/MediaGalleryMetadata/etc/di.xml @@ -112,6 +112,7 @@ <argument name="segmentReaders" xsi:type="array"> <item name="xmp" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadXmp</item> <item name="iptc" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadIptc</item> + <item name="exif" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadExif</item> </argument> </arguments> </virtualType> From 4a7fbd733c59ce798d8fbdbad790da19baaa5a61 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 14:31:35 +0300 Subject: [PATCH 50/57] Add validation --- .../MediaGalleryMetadata/Model/Png/Segment/ReadExif.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php index cd1160ed92589..94e5f4a9ef0e9 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -39,6 +39,12 @@ public function __construct( */ 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); From 5e072905804d2a66283cbd63151eb598e209ab09 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 14:34:06 +0300 Subject: [PATCH 51/57] Remove obsolete tests --- .../Model/Jpeg/Segment/ExifTest.php | 132 ------------------ 1 file changed, 132 deletions(-) delete mode 100644 app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php deleted file mode 100644 index 93c4ced52bc9f..0000000000000 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/Jpeg/Segment/ExifTest.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGalleryMetadata\Test\Integration\Model\Jpeg\Segment; - -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\DriverInterface; -use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; -use Magento\MediaGalleryMetadata\Model\Jpeg\ReadFile; -use Magento\MediaGalleryMetadata\Model\MetadataFactory; - -/** - * Test for EXIF reader - */ -class ExifTest extends TestCase -{ - /** - * @var WriteIptc - */ - private $iptcWriter; - - /** - * @var ReadIptc - */ - private $iptcReader; - - /** - * @var DriverInterface - */ - private $driver; - - /** - * @var ReadFile - */ - private $fileReader; - - /** - * @var MetadataFactory - */ - private $metadataFactory; - - /** - * @var WriteInterface - */ - private $varDirectory; - - /** - * @inheritdoc - */ - protected function setUp(): void - { - $this->varDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) - ->getDirectoryWrite(DirectoryList::VAR_DIR); - $this->iptcWriter = Bootstrap::getObjectManager()->get(WriteIptc::class); - $this->iptcReader = Bootstrap::getObjectManager()->get(ReadIptc::class); - $this->fileReader = Bootstrap::getObjectManager()->get(ReadFile::class); - $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); - $this->metadataFactory = Bootstrap::getObjectManager()->get(MetadataFactory::class); - } - - /** - * Test for IPTC reader and writer - * - * @dataProvider filesProvider - * @param string $fileName - * @param string $title - * @param string $description - * @param array $keywords - * @throws LocalizedException - */ - public function testWriteRead( - string $fileName, - string $title, - string $description, - array $keywords - ): void { - $path = realpath(__DIR__ . '/../../../../_files/' . $fileName); - $modifiableFilePath = $this->varDirectory->getAbsolutePath($fileName); - $this->driver->copy( - $path, - $modifiableFilePath - ); - $modifiableFilePath = $this->fileReader->execute($modifiableFilePath); - $originalMetadata = $this->iptcReader->execute($modifiableFilePath); - - $this->assertEmpty($originalMetadata->getTitle()); - $this->assertEmpty($originalMetadata->getDescription()); - $this->assertEmpty($originalMetadata->getKeywords()); - - $updatedFile = $this->iptcWriter->execute( - $modifiableFilePath, - $this->metadataFactory->create([ - 'title' => $title, - 'description' => $description, - 'keywords' => $keywords - ]) - ); - - $updatedMetadata = $this->iptcReader->execute($updatedFile); - - $this->assertEquals($title, $updatedMetadata->getTitle()); - $this->assertEquals($description, $updatedMetadata->getDescription()); - $this->assertEquals($keywords, $updatedMetadata->getKeywords()); - } - - /** - * Data provider for testExecute - * - * @return array[] - */ - public function filesProvider(): array - { - return [ - [ - 'empty_iptc.jpeg', - 'Updated Title', - 'Updated Description', - [ - 'magento2', - 'mediagallery' - ] - ] - ]; - } -} From 0830d38e4b44d145cca9425bdfdefb7509a3df99 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 14:36:07 +0300 Subject: [PATCH 52/57] Update docBlock --- .../Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php index 94e5f4a9ef0e9..4c13d9a97255f 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -61,7 +61,7 @@ public function execute(FileInterface $file): MetadataInterface /** * Parese exif data from segment * - * @param FileInterface $filePath + * @param SegmentInterface $segment */ private function getExifData(SegmentInterface $segment): MetadataInterface { From bed58c5ccca7b11d2495d44ce6cebdc03c49439b Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 15:49:37 +0300 Subject: [PATCH 53/57] Fix static tests --- .../Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php | 1 + .../Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php index 5e5deb0e119fc..edacd8bb65cec 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -12,6 +12,7 @@ use Magento\MediaGalleryMetadataApi\Model\FileInterface; use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; +use Magento\Framework\Exception\LocalizedException; /** * Jpeg EXIF Reader diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php index 4c13d9a97255f..a0e70eef62a0b 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -12,6 +12,7 @@ use Magento\MediaGalleryMetadataApi\Model\FileInterface; use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; +use Magento\Framework\Exception\LocalizedException; /** * Jpeg EXIF Reader From 4ebe9d6397312bda6da09654c5fac51391844757 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 16:18:15 +0300 Subject: [PATCH 54/57] Fix ui-select options placeholders for url-filter-applier && Clean cached options for ui-select component after closing slide form --- ...lleryAssetFilterPlaceHolderActionGroup.xml | 20 +++ ...diaGalleryCatalogUiCategoryGridSection.xml | 1 + ...alleryCatalogUiUsedInProductFilterTest.xml | 10 +- ...alogUiVerifyUsedInLinkCategoryGridTest.xml | 3 + .../Listing/Filters/UsedInProducts.php | 68 +++------ .../Listing/Filters/UsedInBlocks.php | 66 +++----- .../Component/Listing/Filters/UsedInPages.php | 67 +++----- .../Controller/Adminhtml/Asset/Search.php | 2 +- ...diaGalleryFilterPlaceholderActionGroup.xml | 20 +++ ...dminEnhancedMediaGalleryFiltersSection.xml | 1 + .../Ui/Component/Listing/Filters/Asset.php | 143 ++++++++++++++++-- .../adminhtml/web/js/image/image-actions.js | 1 + .../grid/filters/elements/ui-select.html | 8 +- 13 files changed, 266 insertions(+), 144 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup"> + <annotations> + <description>Assert asset filter placeholder value</description> + </annotations> + <arguments> + <argument name="filterPlaceholder" type="string"/> + </arguments> + + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.activeFilterPlaceholder(filterPlaceholder)}}" stepKey="assertFilterPLaceHolder" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 5267a215c8edd..e5ad84ac5b9df 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,6 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"> + <element name="activeFilterPlaceholder" type="text" selector="//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> <element name="path" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Path')]/preceding-sibling::th)]" parameterized="true"/> <element name="name" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Name')]/preceding-sibling::th) +1 ]//*[text()='{{categoryName}}']" parameterized="true"/> <element name="displayMode" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Display Mode')]/preceding-sibling::th) +1 ]//*[text()='{{productsText}}']" parameterized="true"/> 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 @@ <actionGroup ref="AdminMediaGalleryAssertImageInGridActionGroup" stepKey="assertImageInGrid"> <argument name="title" value="ImageMetadata.title"/> </actionGroup> + + <wait time="10" stepKey="waitForBookmarkToSaveView"/> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForGridReloaded"/> + <actionGroup ref="AdminAssertMediaGalleryFilterPlaceholderActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="$$product.name$$"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> <argument name="imageName" value="{{ImageMetadata.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> - + </test> </tests> 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 @@ <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInCategories"> <argument name="entityName" value="Categories"/> </actionGroup> + <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> + </actionGroup> <actionGroup ref="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup" stepKey="assertCategoryInGrid"> <argument name="categoryName" value="$$category.name$$"/> </actionGroup> 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/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/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/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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertMediaGalleryFilterPlaceholderActionGroup"> + <annotations> + <description>Assert asset filter placeholder value</description> + </annotations> + <arguments> + <argument name="filterPlaceholder" type="string"/> + </arguments> + + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.activeFilterPlaceholder(filterPlaceholder)}}" stepKey="assertFilterPLaceHolder" /> + </actionGroup> +</actionGroups> 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 @@ <element name="searchOptionsFilterOption" type="text" selector="//div[label/span[contains(text(), '{{filterName}}')]]//label[@class='admin__action-multiselect-label']/span[text()='{{optionName}}']" parameterized="true" timeout="30"/> <element name="searchOptionsFilterDone" type="button" selector="//div[label/span[contains(text(), '{{filterName}}')]]//button[@data-action='close-advanced-select']" parameterized="true"/> <element name="duplicatedFilterCheckbox" type="button" selector="//input[@name='duplicated']"/> + <element name="activeFilterValue" type="text" selector="//div[@class='media-gallery-container']//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> </section> </sections> 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/view/adminhtml/web/js/image/image-actions.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js index c7ca95bed863c..dfd4420c701bb 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'); }, 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"> From 58fbad4730f1dd552ee90b0008bc99cef77d644b Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Mon, 17 Aug 2020 21:23:21 +0300 Subject: [PATCH 55/57] Improve metadata types && isExifSegment() verification for jpeg --- .../Model/GetIptcMetadata.php | 7 +++---- .../Model/Jpeg/Segment/ReadExif.php | 11 +++++------ .../Model/Jpeg/Segment/ReadIptc.php | 6 +++--- .../Model/Jpeg/Segment/ReadXmp.php | 6 +++--- .../Model/Png/Segment/ReadExif.php | 1 - .../Model/Png/Segment/ReadXmp.php | 6 +++--- .../Test/_files/empty_iptc.jpeg | Bin 19416 -> 19416 bytes 7 files changed, 17 insertions(+), 20 deletions(-) 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 index edacd8bb65cec..b6c32296f3f7d 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -70,20 +70,19 @@ private function getExifData(string $filePath): MetadataInterface { $title = null; $description = null; - $keywords = []; + $keywords = null; $data = exif_read_data($filePath); - if ($data) { + if (!empty($data)) { $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; - $keywords = ''; } return $this->metadataFactory->create([ 'title' => $title, 'description' => $description, - 'keywords' => !empty($keywords) ? $keywords : null + 'keywords' => $keywords ]); } @@ -97,9 +96,9 @@ private function isExifSegment(SegmentInterface $segment): bool { return $segment->getName() === self::EXIF_SEGMENT_NAME && strncmp( - substr($segment->getData(), self::EXIF_DATA_START_POSITION, 4), + substr($segment->getData(), self::EXIF_DATA_START_POSITION, 5), self::EXIF_SEGMENT_START, - self::EXIF_DATA_START_POSITION + 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 index a0e70eef62a0b..81ecf57efaf32 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -75,7 +75,6 @@ private function getExifData(SegmentInterface $segment): MetadataInterface if ($data) { $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; - $keywords = ''; } return $this->metadataFactory->create([ 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/_files/empty_iptc.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg index 144a56dac2d3e748d47cf56e0fdde0bab2f49def..1a345c2d33fdddfb2a66fee2f40d822d8604d029 100644 GIT binary patch delta 29 XcmcaHo$<zW#tBja3}|4Z);ezhV_*eu delta 29 hcmcaHo$<zW#tBjatPBhcQa}s>d5O8H8@1MX0|0OW2QL5s From 9a740371e1bfdde8c5dc79460de6e6e4a29dc27f Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 18 Aug 2020 10:27:15 +0300 Subject: [PATCH 56/57] Improve comparsion remove exif data from other images --- .../Model/Png/Segment/ReadExif.php | 3 +-- .../Test/_files/macos-preview.png | Bin 57535 -> 57155 bytes 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php index 81ecf57efaf32..98c763d131b22 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -70,7 +70,6 @@ private function getExifData(SegmentInterface $segment): MetadataInterface $description = null; $keywords = []; - $data = exif_read_data('data://image/jpeg;base64,' . base64_encode($segment->getData())); if ($data) { $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; @@ -92,6 +91,6 @@ private function getExifData(SegmentInterface $segment): MetadataInterface */ private function isExifSegment(SegmentInterface $segment): bool { - return $segment->getName() === self::EXIF_SEGMENT_NAME; + return strcmp($segment->getName(), self::EXIF_SEGMENT_NAME) === 0; } } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png index 966520f0d01124eec138a9e29298dbe8e2fea5ab..95eb45f69b3eafcedbadbb204baabb01b3bec775 100644 GIT binary patch delta 13 VcmdmgkooXF<_QLy*Dwm-2LLUY1{weW delta 400 zcmX@Sk9q$=<_QM%j0LF?o@u_m3|b5f3>*yXjC>4CK$ap9Cou{!Fav2uAY@>aVqgWc z85mj^rQz%zMh&PMpe{xuuwD_Mx+(3M3@lLfD}XeEOKNd)QD#9&W`3SRewso_Myf(? zVtVRie`d~lkUYZ#AO_k4p^XfT46F>ytc;8lj0~+zjI4|e7#O%FFvHAeRGYv8XIo7W z1hd(J2KY@7g0sUWwJ`w27=(c0ag!#3#aYaOCQX`zU@u@~sE2!U0Tay8Op_Kc!`KXe zfQB$&a}qW&Z39Cq1E6bRv}3TdrzcQ<aB@*<YF=?heu<04`Po1L&H|6fVg?2=)s0r1 F_W}GlJTU+O From 583b0134ff6244438e55064bcd9b2980a18d2bc9 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 18 Aug 2020 10:32:06 +0300 Subject: [PATCH 57/57] rever removed line by php-cs-fixer --- .../Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php index 98c763d131b22..09aeaf526443a 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -70,6 +70,7 @@ private function getExifData(SegmentInterface $segment): MetadataInterface $description = null; $keywords = []; + $data = exif_read_data('data://image/jpeg;base64,' . base64_encode($segment->getData())); if ($data) { $title = isset($data['DocumentName']) ? $data['DocumentName'] : null;