From 1e8e825abdc37dbd9ea196867552169a91cad082 Mon Sep 17 00:00:00 2001 From: korostii <24894168+korostii@users.noreply.github.com> Date: Sat, 4 Apr 2020 14:17:53 +0000 Subject: [PATCH 01/95] Fix #27523: throw informative error if the requested module does not exist --- .../Magento/Developer/Console/Command/GeneratePatchCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php index 78531c7e6c22c..3093c06962080 100644 --- a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php +++ b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php @@ -133,6 +133,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $type = $input->getOption(self::INPUT_KEY_PATCH_TYPE); $modulePath = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName); + if (null === $modulePath) { + throw new \InvalidArgumentException(sprintf('Cannot find a registered module with name "%s"', $moduleName)); + } $preparedModuleName = str_replace('_', '\\', $moduleName); $preparedType = ucfirst($type); $patchInterface = sprintf('%sPatchInterface', $preparedType); From 1dd5290fcb7b379a9fdad38136a7dd13ae907fa3 Mon Sep 17 00:00:00 2001 From: korostii <24894168+korostii@users.noreply.github.com> Date: Sat, 4 Apr 2020 15:02:53 +0000 Subject: [PATCH 02/95] Fix #27523: add tests for GeneratePatchCommand --- .../Command/GeneratePatchCommandTest.php | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php new file mode 100644 index 0000000000000..b9482ce07eb2d --- /dev/null +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php @@ -0,0 +1,118 @@ +componentRegistrar = $this->createMock(ComponentRegistrar::class); + $this->directoryList = $this->createMock(DirectoryList::class); + $this->readFactory = $this->createMock(ReadFactory::class); + $this->writeFactory = $this->createMock(WriteFactory::class); + + $this->command = new GeneratePatchCommand( + $this->componentRegistrar, + $this->directoryList, + $this->readFactory, + $this->writeFactory + ); + } + + public function testExecute() + { + $this->componentRegistrar->expects($this->once()) + ->method('getPath') + ->with('module', 'Vendor_Module') + ->willReturn('/long/path/to/Vendor/Module'); + + $read = $this->createMock(\Magento\Framework\Filesystem\Directory\Read::class); + $read->expects($this->at(0)) + ->method('readFile') + ->with('patch_template.php.dist') + ->willReturn('something'); + $this->readFactory->method('create')->willReturn($read); + + $write = $this->createMock(\Magento\Framework\Filesystem\Directory\Write::class); + $write->expects($this->once())->method('writeFile'); + $this->writeFactory->method('create')->willReturn($write); + + $this->directoryList->expects($this->once())->method('getRoot')->willReturn('/some/path'); + + $commandTester = new CommandTester($this->command); + $commandTester->execute( + [ + GeneratePatchCommand::MODULE_NAME => 'Vendor_Module', + GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' + ] + ); + $this->assertContains('successfully generated', $commandTester->getDisplay()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Not enough arguments + */ + public function testWrongParameter() + { + $commandTester = new CommandTester($this->command); + $commandTester->execute([]); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cannot find a registered module with name "Fake_Module" + */ + public function testBadModule() + { + $this->componentRegistrar->expects($this->once()) + ->method('getPath') + ->with('module', 'Fake_Module') + ->willReturn(null); + + $commandTester = new CommandTester($this->command); + $commandTester->execute( + [ + GeneratePatchCommand::MODULE_NAME => 'Fake_Module', + GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' + ] + ); + $this->assertContains('successfully generated', $commandTester->getDisplay()); + } +} From c1d8feffe1c547eacbfbfc031708be51e0787503 Mon Sep 17 00:00:00 2001 From: korostii <24894168+korostii@users.noreply.github.com> Date: Sat, 4 Apr 2020 19:05:31 +0300 Subject: [PATCH 03/95] Fix #27523: fix tests for phpunit8 and code style --- .../Command/GeneratePatchCommandTest.php | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php index b9482ce07eb2d..2047c17c40b15 100644 --- a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php @@ -8,72 +8,76 @@ use Magento\Developer\Console\Command\GeneratePatchCommand; use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Filesystem\Directory\Read; use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\Filesystem\Directory\Write; use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Filesystem\DirectoryList; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; -class GeneratePatchCommandTest extends \PHPUnit\Framework\TestCase +class GeneratePatchCommandTest extends TestCase { /** - * @var ComponentRegistrar + * @var ComponentRegistrar|MockObject */ - private $componentRegistrar; + private $componentRegistrarMock; /** - * @var DirectoryList + * @var DirectoryList|MockObject */ - private $directoryList; + private $directoryListMock; /** - * @var ReadFactory + * @var ReadFactory|MockObject */ - private $readFactory; + private $readFactoryMock; /** - * @var WriteFactory + * @var WriteFactory|MockObject */ - private $writeFactory; + private $writeFactoryMock; /** - * @var GeneratePatchCommand + * @var GeneratePatchCommand|MockObject */ private $command; protected function setUp() { - $this->componentRegistrar = $this->createMock(ComponentRegistrar::class); - $this->directoryList = $this->createMock(DirectoryList::class); - $this->readFactory = $this->createMock(ReadFactory::class); - $this->writeFactory = $this->createMock(WriteFactory::class); + $this->componentRegistrarMock = $this->createMock(ComponentRegistrar::class); + $this->directoryListMock = $this->createMock(DirectoryList::class); + $this->readFactoryMock = $this->createMock(ReadFactory::class); + $this->writeFactoryMock = $this->createMock(WriteFactory::class); $this->command = new GeneratePatchCommand( - $this->componentRegistrar, - $this->directoryList, - $this->readFactory, - $this->writeFactory + $this->componentRegistrarMock, + $this->directoryListMock, + $this->readFactoryMock, + $this->writeFactoryMock ); } public function testExecute() { - $this->componentRegistrar->expects($this->once()) + $this->componentRegistrarMock->expects($this->once()) ->method('getPath') ->with('module', 'Vendor_Module') ->willReturn('/long/path/to/Vendor/Module'); - $read = $this->createMock(\Magento\Framework\Filesystem\Directory\Read::class); + $read = $this->createMock(Read::class); $read->expects($this->at(0)) ->method('readFile') ->with('patch_template.php.dist') ->willReturn('something'); - $this->readFactory->method('create')->willReturn($read); + $this->readFactoryMock->method('create')->willReturn($read); - $write = $this->createMock(\Magento\Framework\Filesystem\Directory\Write::class); + $write = $this->createMock(Write::class); $write->expects($this->once())->method('writeFile'); - $this->writeFactory->method('create')->willReturn($write); + $this->writeFactoryMock->method('create')->willReturn($write); - $this->directoryList->expects($this->once())->method('getRoot')->willReturn('/some/path'); + $this->directoryListMock->expects($this->once())->method('getRoot')->willReturn('/some/path'); $commandTester = new CommandTester($this->command); $commandTester->execute( @@ -85,27 +89,25 @@ public function testExecute() $this->assertContains('successfully generated', $commandTester->getDisplay()); } - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Not enough arguments - */ public function testWrongParameter() { + $this->expectExceptionMessage('Not enough arguments'); + $this->expectException(\RuntimeException::class); + $commandTester = new CommandTester($this->command); $commandTester->execute([]); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Cannot find a registered module with name "Fake_Module" - */ public function testBadModule() { - $this->componentRegistrar->expects($this->once()) + $this->componentRegistrarMock->expects($this->once()) ->method('getPath') ->with('module', 'Fake_Module') ->willReturn(null); + $this->expectExceptionMessage('Cannot find a registered module with name "Fake_Module"'); + $this->expectException(\InvalidArgumentException::class); + $commandTester = new CommandTester($this->command); $commandTester->execute( [ @@ -113,6 +115,5 @@ public function testBadModule() GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' ] ); - $this->assertContains('successfully generated', $commandTester->getDisplay()); } } From 766a6ab88961289c46e7441be0d26b66f2f0ef75 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz Date: Sun, 5 Apr 2020 04:26:09 +0200 Subject: [PATCH 04/95] magento/magento2#27357 Fix the test coverage for e-mail contents --- .../Newsletter/Model/SubscriberTest.php | 73 ++++++++++++++----- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 06c8902f45897..0fdcfdb659b5d 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -8,6 +8,7 @@ namespace Magento\Newsletter\Model; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Mail\EmailMessage; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Mail\Template\TransportBuilderMock; @@ -20,13 +21,16 @@ */ class SubscriberTest extends TestCase { - /** @var ObjectManagerInterface */ + private const CONFIRMATION_SUBSCRIBE = 'You have been successfully subscribed to our newsletter.'; + const CONFIRMATION_UNSUBSCRIBE = 'You have been unsubscribed from the newsletter.'; + + /** @var ObjectManagerInterface */ private $objectManager; /** @var SubscriberFactory */ private $subscriberFactory; - /** @var TransportBuilderMock */ + /** @var TransportBuilderMock */ private $transportBuilder; /** @var CustomerRepositoryInterface */ @@ -87,17 +91,19 @@ public function testUnsubscribeSubscribe(): void $subscriber = $this->subscriberFactory->create(); $this->assertSame($subscriber, $subscriber->loadByCustomerId(1)); $this->assertEquals($subscriber, $subscriber->unsubscribe()); - $this->assertContains( - 'You have been unsubscribed from the newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_UNSUBSCRIBE, + $this->transportBuilder->getSentMessage() ); + $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); // Subscribe and verify $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->subscribe('customer@example.com')); $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertContains( - 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() ); } @@ -114,16 +120,17 @@ public function testUnsubscribeSubscribeByCustomerId(): void // Unsubscribe and verify $this->assertSame($subscriber, $subscriber->unsubscribeCustomerById(1)); $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertContains( - 'You have been unsubscribed from the newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_UNSUBSCRIBE, + $this->transportBuilder->getSentMessage() ); + // Subscribe and verify $this->assertSame($subscriber, $subscriber->subscribeCustomerById(1)); $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertContains( - 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() ); } @@ -141,9 +148,10 @@ public function testConfirm(): void $subscriber->subscribe($customerEmail); $subscriber->loadByEmail($customerEmail); $subscriber->confirm($subscriber->getSubscriberConfirmCode()); - $this->assertContains( - 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() ); } @@ -174,4 +182,35 @@ public function testSubscribeUnconfirmedCustomerWithoutSubscription(): void $subscriber->subscribeCustomerById($customer->getId()); $this->assertEquals(Subscriber::STATUS_UNCONFIRMED, $subscriber->getStatus()); } + + /** + * Verifies if Paragraph with specified message is in e-mail + * + * @param string $expectedMessage + * @param EmailMessage $message + */ + private function assertConfirmationParagraphExists(string $expectedMessage, EmailMessage $message): void + { + $messageContent = $this->getMessageRawContent($message); + + $emailDom = new \DOMDocument(); + $emailDom->loadHTML($messageContent); + + $emailXpath = new \DOMXPath($emailDom); + $greeting = $emailXpath->query("//p[contains(text(), '$expectedMessage')]"); + + $this->assertSame(1, $greeting->length, "Cannot find the confirmation paragraph in e-mail contents"); + } + + /** + * Returns raw content of provided message + * + * @param EmailMessage $message + * @return string + */ + private function getMessageRawContent(EmailMessage $message): string + { + $emailParts = $message->getBody()->getParts(); + return current($emailParts)->getRawContent(); + } } From dcd807bd2e8500bd4a53916a5307fa9e26eb4035 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz Date: Sun, 5 Apr 2020 04:28:13 +0200 Subject: [PATCH 05/95] magento/magento2#27357 Add missing visibility --- .../testsuite/Magento/Newsletter/Model/SubscriberTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 0fdcfdb659b5d..6715304239eb6 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -22,7 +22,7 @@ class SubscriberTest extends TestCase { private const CONFIRMATION_SUBSCRIBE = 'You have been successfully subscribed to our newsletter.'; - const CONFIRMATION_UNSUBSCRIBE = 'You have been unsubscribed from the newsletter.'; + private const CONFIRMATION_UNSUBSCRIBE = 'You have been unsubscribed from the newsletter.'; /** @var ObjectManagerInterface */ private $objectManager; From a6f460dbe499df42c69af40b0fd51e05d0d6aa39 Mon Sep 17 00:00:00 2001 From: "v.prokopov" Date: Thu, 28 May 2020 16:29:40 +0300 Subject: [PATCH 06/95] fixed refund issue for downloadable items --- .../Observer/SetLinkStatusObserver.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php index 971feafb857a9..a450651952a7e 100644 --- a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php +++ b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php @@ -61,6 +61,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) 'payment_pending' => \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_PENDING_PAYMENT, 'payment_review' => \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_PAYMENT_REVIEW, ]; + $expiredOrderItemIds = []; $downloadableItemsStatuses = []; $orderItemStatusToEnable = $this->_scopeConfig->getValue( @@ -114,6 +115,10 @@ public function execute(\Magento\Framework\Event\Observer $observer) if (in_array($item->getStatusId(), $availableStatuses)) { $downloadableItemsStatuses[$item->getId()] = $linkStatuses['avail']; } + + if ($item->getQtyOrdered() - $item->getQtyRefunded() == 0) { + $expiredOrderItemIds[] = $item->getId(); + } } } } @@ -141,6 +146,16 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } + if ($expiredOrderItemIds) { + $linkPurchased = $this->_createItemsCollection()->addFieldToFilter( + 'order_item_id', + ['in' => $expiredOrderItemIds] + ); + foreach ($linkPurchased as $link) { + $link->setStatus(\Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED)->save(); + } + } + return $this; } From 9a83830b52446566d64531b267e0132800ae1ac4 Mon Sep 17 00:00:00 2001 From: "v.prokopov" Date: Thu, 28 May 2020 20:44:46 +0300 Subject: [PATCH 07/95] added unit test --- .../Observer/SetLinkStatusObserverTest.php | 148 +++++++++++++++++- 1 file changed, 141 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php b/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php index 46a3ef6717582..b5be0309bb5be 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php @@ -189,7 +189,7 @@ public function testSetLinkStatusPending($orderState, array $orderStateMapping) ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -243,7 +243,7 @@ public function testSetLinkStatusClosed() ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -308,7 +308,7 @@ public function testSetLinkStatusInvoiced() ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -344,6 +344,137 @@ public function testSetLinkStatusEmptyOrder() $this->assertInstanceOf(SetLinkStatusObserver::class, $result); } + public function testSetLinkStatusExpired() + { + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->with( + \Magento\Downloadable\Model\Link\Purchased\Item::XML_PATH_ORDER_ITEM_STATUS, + ScopeInterface::SCOPE_STORE, + 1 + ) + ->willReturn(Item::STATUS_PENDING); + + $this->observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($this->eventMock); + + $this->eventMock->expects($this->once()) + ->method('getOrder') + ->willReturn($this->orderMock); + + $this->orderMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->orderMock->expects($this->once()) + ->method('getStoreId') + ->willReturn(1); + + $this->orderMock->expects($this->atLeastOnce()) + ->method('getState') + ->willReturn(Order::STATE_PROCESSING); + + $this->orderMock->expects($this->any()) + ->method('getAllItems') + ->willReturn( + [ + $this->createRefundOrderItem(2, 2, 2), + $this->createRefundOrderItem(3, 2, 1), + $this->createRefundOrderItem(4, 3, 3), + ] + ); + + $this->itemsFactory->expects($this->any()) + ->method('create') + ->willReturn( + $this->createLinkItemToExpireCollection( + [2, 4], + [ + $this->createLinkItem( + 'available', + 2, + true, + \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED + ), + $this->createLinkItem( + 'pending_payment', + 4, + true, + \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED + ), + ] + ) + ); + + $result = $this->setLinkStatusObserver->execute($this->observerMock); + $this->assertInstanceOf(SetLinkStatusObserver::class, $result); + } + + /** + * @param $id + * @param int $qtyOrdered + * @param int $qtyRefunded + * @param string $productType + * @param string $realProductType + * @return \Magento\Sales\Model\Order\Item|MockObject + */ + private function createRefundOrderItem( + $id, + $qtyOrdered, + $qtyRefunded, + $productType = DownloadableProductType::TYPE_DOWNLOADABLE, + $realProductType = DownloadableProductType::TYPE_DOWNLOADABLE + ) { + $item = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getId', + 'getQtyOrdered', + 'getQtyRefunded', + 'getProductType', + 'getRealProductType' + ])->getMock(); + $item->expects($this->any()) + ->method('getId') + ->willReturn($id); + $item->expects($this->any()) + ->method('getQtyOrdered') + ->willReturn($qtyOrdered); + $item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn($qtyRefunded); + $item->expects($this->any()) + ->method('getProductType') + ->willReturn($productType); + $item->expects($this->any()) + ->method('getRealProductType') + ->willReturn($realProductType); + + return $item; + } + + /** + * @param array $expectedOrderItemIds + * @param array $items + * @return LinkItemCollection|MockObject + */ + private function createLinkItemToExpireCollection(array $expectedOrderItemIds, array $items) + { + $linkItemCollection = $this->getMockBuilder( + \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\Collection::class + ) + ->disableOriginalConstructor() + ->setMethods(['addFieldToFilter']) + ->getMock(); + $linkItemCollection->expects($this->any()) + ->method('addFieldToFilter') + ->with('order_item_id', ['in' => $expectedOrderItemIds]) + ->willReturn($items); + + return $linkItemCollection; + } + /** * @param $id * @param int $statusId @@ -359,7 +490,7 @@ private function createOrderItem( ) { $item = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() - ->setMethods(['getId', 'getProductType', 'getRealProductType', 'getStatusId']) + ->setMethods(['getId', 'getProductType', 'getRealProductType', 'getStatusId', 'getQtyOrdered']) ->getMock(); $item->expects($this->any()) ->method('getId') @@ -373,6 +504,9 @@ private function createOrderItem( $item->expects($this->any()) ->method('getStatusId') ->willReturn($statusId); + $item->expects($this->any()) + ->method('getQtyOrdered') + ->willReturn(1); return $item; } @@ -390,7 +524,7 @@ private function createLinkItemCollection(array $expectedOrderItemIds, array $it ->disableOriginalConstructor() ->setMethods(['addFieldToFilter']) ->getMock(); - $linkItemCollection->expects($this->once()) + $linkItemCollection->expects($this->any()) ->method('addFieldToFilter') ->with('order_item_id', ['in' => $expectedOrderItemIds]) ->willReturn($items); @@ -415,11 +549,11 @@ private function createLinkItem($status, $orderItemId, $isSaved = false, $expect ->method('getStatus') ->willReturn($status); if ($isSaved) { - $linkItem->expects($this->once()) + $linkItem->expects($this->any()) ->method('setStatus') ->with($expectedStatus) ->willReturnSelf(); - $linkItem->expects($this->once()) + $linkItem->expects($this->any()) ->method('save') ->willReturnSelf(); } From 59cf9f7d321fdc4278478e9e656f64584e95b80c Mon Sep 17 00:00:00 2001 From: "v.prokopov" Date: Fri, 29 May 2020 08:54:14 +0300 Subject: [PATCH 08/95] added short description --- .../Magento/Downloadable/Observer/SetLinkStatusObserver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php index a450651952a7e..2a07a3a49639f 100644 --- a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php +++ b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php @@ -160,6 +160,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) } /** + * Returns purchased item collection + * * @return \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\Collection */ protected function _createItemsCollection() From 1f7eace566744d292a5430afc38ec833e6f06f79 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk Date: Wed, 10 Jun 2020 16:02:38 +0300 Subject: [PATCH 09/95] MFTF test. --- ...roductLinkInCustomerAccountActionGroup.xml | 26 +++++ ...ontCustomerDownloadableProductsSection.xml | 1 + ...dableProductLinkAfterPartialRefundTest.xml | 107 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml new file mode 100644 index 0000000000000..ae288c7033e17 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + Goes to the Storefront Customer Dashboard page. Clicks on 'My Downloadable Products'. Validates that the provided Downloadable Product is present and Downloadable link not exist. + + + + + + + + + + + + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml index d45a774077ba0..5d340e6c91060 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml new file mode 100644 index 0000000000000..3659fd882c5d3 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -0,0 +1,107 @@ + + + + + + + + + <description value="Verify that Downloadable product is not available in My Download Products tab after it has been partially refunded."/> + <severity value="CRITICAL"/> + <group value="Downloadable"/> + </annotations> + + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signIn"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <actionGroup ref="StorefrontAddSimpleProductToShoppingCartActionGroup" stepKey="addSimpleProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <waitForElementVisible selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="waitForShipHereVisible"/> + <click selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="clickShipHere"/> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> + <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchOrder"> + <argument name="keyword" value="$grabOrderNumber"/> + </actionGroup> + <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> + + <actionGroup ref="AdminCreateInvoiceActionGroup" stepKey="createCreditMemo"/> + + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> + + <actionGroup ref="AdminOpenAndFillCreditMemoRefundActionGroup" stepKey="fillCreditMemoRefund"> + <argument name="itemQtyToRefund" value="0"/> + <argument name="rowNumber" value="1"/> + </actionGroup> + + <click selector="{{AdminCreditMemoTotalSection.submitRefundOffline}}" stepKey="clickRefundOffline"/> + <waitForPageLoad stepKey="waitForResultPage"/> + + <actionGroup ref="StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup" stepKey="dontSeeStorefrontMyAccountDownloadableProductsLink"> + <argument name="product" value="$$createDownloadableProduct$$"/> + </actionGroup> + + </test> +</tests> From db84d3afad32c2e6a361a19ade83b86d8236469d Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Tue, 16 Jun 2020 14:20:46 +0300 Subject: [PATCH 10/95] MFTF test, updated testCaseId. --- ...frontAccountDownloadableProductLinkAfterPartialRefundTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml index 3659fd882c5d3..eaff4a5b116c3 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -14,6 +14,7 @@ <title value="My Account Downloadable Product Link after Partially Refunded"/> <description value="Verify that Downloadable product is not available in My Download Products tab after it has been partially refunded."/> <severity value="CRITICAL"/> + <testCaseId value="MC-35198"/> <group value="Downloadable"/> </annotations> From a49709f0ba48954ff21b16e0314f46be23b3ef3e Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Fri, 26 Jun 2020 15:54:26 +0300 Subject: [PATCH 11/95] Fix MFTF tests. --- ...ProductAndProductCategoryPartialReindexTest.xml | 14 ++++++++------ .../AdminCustomerFindWishlistItemActionGroup.xml | 2 +- .../Test/AdminDeleteCustomerWishListItemTest.xml | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index b4514c9b53736..75876352e5eda 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -55,9 +55,9 @@ <argument name="categoryName" value="$$categoryN.name$$, $$categoryM.name$$"/> </actionGroup> - <wait stepKey="waitBeforeRunCronIndex" time="30"/> + <wait stepKey="waitBeforeRunCronIndex" time="60"/> <magentoCLI stepKey="runCronIndex" command="cron:run --group=index"/> - <wait stepKey="waitAfterRunCronIndex" time="60"/> + <wait stepKey="waitAfterRunCronIndex" time="120"/> </before> <after> <!-- Change "Category Products" and "Product Categories" indexers to "Update on Save" mode --> @@ -103,6 +103,8 @@ <argument name="categoryName" value="$$categoryK.name$$"/> </actionGroup> + <wait stepKey="waitAfterAssignCategoryK" time="60"/> + <!-- Unassign category M from Product B --> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="amOnEditCategoryPageB"> <argument name="productId" value="$$productB.id$$"/> @@ -142,9 +144,9 @@ <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductInCategoryN"/> <!-- Run cron --> - <wait stepKey="waitBeforeRunMagentoCron" time="30"/> + <wait stepKey="waitBeforeRunMagentoCron" time="60"/> <magentoCLI stepKey="runMagentoCron" command="cron:run --group=index"/> - <wait stepKey="waitAfterRunMagentoCron" time="60"/> + <wait stepKey="waitAfterRunMagentoCron" time="120"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> <!-- Category K contains only Products A, C --> @@ -208,9 +210,9 @@ <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="productCOnCategoryN"/> <!-- Run Cron once to reindex product changes --> - <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="30"/> + <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="60"/> <magentoCLI stepKey="runCronIndexAfterProductAssignToCategory" command="cron:run --group=index"/> - <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="60"/> + <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="120"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml index bbdc4de330840..cd581ed1836dd 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml @@ -14,6 +14,6 @@ </arguments> <fillField userInput="{{productName}}" selector="{{AdminCustomerWishlistSection.productName}}" stepKey="fillProductNameField"/> <click selector="{{AdminCustomerWishlistSection.searchButton}}" stepKey="clickSearchButton"/> - <waitForPageLoad stepKey="waitForGridLoading"/> + <waitForAjaxLoad time="60" stepKey="waitForLoading"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml index af229b3507077..275106d491078 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml @@ -22,7 +22,6 @@ <createData entity="SimpleProduct" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> </createData> - <magentoCLI command="cron:run --group=index" stepKey="runCronIndexer"/> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> </before> <after> From dd91daefe2956d8c3d2cd18a5d170f4325ae363a Mon Sep 17 00:00:00 2001 From: Michal Derlatka <michal.derlatka1@gmail.com> Date: Mon, 29 Jun 2020 13:12:49 +0200 Subject: [PATCH 12/95] magento/magento2#26425: Fix TestFramework GraphQL headers parsing --- .../Magento/TestFramework/TestCase/GraphQl/Client.php | 2 +- .../testsuite/Magento/GraphQl/CorsHeadersTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index 5af6413840c27..2fe93c02e7adb 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -213,7 +213,7 @@ private function processResponseHeaders(string $headers): array $headerLines = preg_split('/((\r?\n)|(\r\n?))/', $headers); foreach ($headerLines as $headerLine) { - $headerParts = preg_split('/:/', $headerLine); + $headerParts = preg_split('/: /', $headerLine, 2); if (count($headerParts) == 2) { $headersArray[trim($headerParts[0])] = trim($headerParts[1]); } elseif (preg_match('/HTTP\/[\.0-9]+/', $headerLine)) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php index 25c808a549e80..b8f59b34fae0c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php @@ -76,7 +76,7 @@ public function testCorsHeadersWhenCorsIsEnabled(): void $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_HEADERS, 'Origin'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOW_CREDENTIALS, '1'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_METHODS, 'GET,POST'); - $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_ORIGINS, 'magento.local'); + $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_ORIGINS, 'http://magento.local'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_MAX_AGE, '86400'); $this->reinitConfig->reinit(); @@ -85,7 +85,7 @@ public function testCorsHeadersWhenCorsIsEnabled(): void self::assertEquals('Origin', $headers['Access-Control-Allow-Headers']); self::assertEquals('1', $headers['Access-Control-Allow-Credentials']); self::assertEquals('GET,POST', $headers['Access-Control-Allow-Methods']); - self::assertEquals('magento.local', $headers['Access-Control-Allow-Origin']); + self::assertEquals('http://magento.local', $headers['Access-Control-Allow-Origin']); self::assertEquals('86400', $headers['Access-Control-Max-Age']); } From 75bb2c3537b3a4c7ef719cca36d099bc6af9ef6c Mon Sep 17 00:00:00 2001 From: korostii <24894168+korostii@users.noreply.github.com> Date: Thu, 9 Jul 2020 23:01:25 +0300 Subject: [PATCH 13/95] Fix #27523: fix tests for phpunit9 and php7.4 --- .../Test/Unit/Console/Command/GeneratePatchCommandTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php index 2047c17c40b15..6fa1ca8a4674a 100644 --- a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php @@ -44,7 +44,7 @@ class GeneratePatchCommandTest extends TestCase */ private $command; - protected function setUp() + protected function setUp(): void { $this->componentRegistrarMock = $this->createMock(ComponentRegistrar::class); $this->directoryListMock = $this->createMock(DirectoryList::class); @@ -86,7 +86,7 @@ public function testExecute() GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' ] ); - $this->assertContains('successfully generated', $commandTester->getDisplay()); + $this->assertStringContainsString('successfully generated', $commandTester->getDisplay()); } public function testWrongParameter() From e6aca3b93d6b56357f70b92c47d521acc88102f5 Mon Sep 17 00:00:00 2001 From: zhartaunik <zhartaunik@gmail.com> Date: Fri, 31 Jul 2020 16:09:35 +0300 Subject: [PATCH 14/95] Test scenario for "Apply shopping cart rule to a single bundle item" (#28921) --- .../CartPriceRuleForBundleProductTest.xml | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml new file mode 100644 index 0000000000000..101c72b78078a --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="CartPriceRuleForBundleProductTest"> + <annotations> + <features value="SalesRule"/> + <stories value="MAGETWO-28921 - Cart Price Rule for bundle products"/> + <title value="Checking Cart Price Rule for bundle products"/> + <description value="Checking Cart Price Rule for bundle products"/> + <severity value="BLOCKER"/> + <testCaseId value="MAGETWO-28921"/> + <group value="SalesRule"/> + </annotations> + + <before> + <!--Create 4 simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">5.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">3.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">7.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">18.00</field> + </createData> + + <!-- Create the bundle product based --> + <createData entity="ApiBundleProduct" stepKey="createBundleProduct" /> + <createData entity="CheckboxOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="CheckboxOption" stepKey="createBundleOption1_2"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct3"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct3"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct4"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct4"/> + </createData> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + <!-- Make Attribute 'sku' accessible for Promo Rule Conditions --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="editSkuAttribute"> + <argument name="ProductAttribute" value="sku" /> + </actionGroup> + <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeAttributePromoRule"> + <argument name="option" value="1" /> + </actionGroup> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices" /> + </before> + + <after> + <!-- Delete created SalesRule --> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="DeleteCartPriceRuleByName"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + + <!-- Delete Bundle product and it's children --> + <deleteData createDataKey="createBundleProduct" stepKey="createBundleProduct" /> + <deleteData createDataKey="simpleProduct1" stepKey="simpleProduct1" /> + <deleteData createDataKey="simpleProduct2" stepKey="simpleProduct2" /> + <deleteData createDataKey="simpleProduct3" stepKey="simpleProduct3" /> + <deleteData createDataKey="simpleProduct4" stepKey="simpleProduct4" /> + + <!-- Revert Attribute 'sku' to it's default value (not accessible for Promo Rule Conditions) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="editSkuAttribute"> + <argument name="ProductAttribute" value="sku" /> + </actionGroup> + <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeAttributePromoRule"> + <argument name="option" value="0" /> + </actionGroup> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices2" /> + </after> + + <!-- Create the rule --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="SelectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="10" stepKey="fillDiscountAmount"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="ScrollToApplyRuleForConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="ApplyRuleForConditions"/> + <waitForPageLoad stepKey="waitForDropDownOpened"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.childAttribute}}" userInput="SKU" stepKey="selectAttribute"/> + <waitForPageLoad stepKey="waitForOperatorOpened"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('is')}}" stepKey="clickToChooseCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.operator}}" userInput="is one of" stepKey="selectOperator"/> + <waitForPageLoad stepKey="waitForOperatorOpened1"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('...')}}" stepKey="clickToChooseOption"/> + <waitForPageLoad stepKey="waitForConditionOpened2"/> + <fillField selector="{{AdminCartPriceRulesFormSection.actionValue}}" userInput="$$simpleProduct1.sku$$" stepKey="fillSkuToFilters"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add the first product to the cart --> + <amOnPage url="$$createBundleProduct.sku$$.html" stepKey="goToProductPage1"/> + <waitForPageLoad stepKey="waitForProductPageLoad1"/> + + <!--Click "Customize and Add to Cart" button--> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!-- Select two products --> + <click stepKey="selectProduct1" selector="{{StorefrontBundledSection.productCheckbox('1','1')}}"/> + <click stepKey="selectProduct2" selector="{{StorefrontBundledSection.productCheckbox('2','1')}}"/> + + <!--Click "Add to Cart" button--> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickAddBundleProductToCart"/> + <waitForPageLoad time="30" stepKey="waitForAddBundleProductPageLoad"/> + + <!--Click "mini cart" icon--> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <waitForPageLoad stepKey="waitForDetailsOpen"/> + + <!--Check all products and Cart Subtotal --> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert" after="waitForDetailsOpen"> + <argument name="subtotal" value="12.00"/> + <argument name="shipping" value="5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="16.50"/> + </actionGroup> + </test> +</tests> From b14022fcee93b94009e64f1e0ba05fe165d1e973 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 31 Jul 2020 10:26:54 -0500 Subject: [PATCH 15/95] MC-36227: Page builder content is getting cropped. --- app/code/Magento/Catalog/etc/db_schema.xml | 4 ++-- app/code/Magento/Eav/etc/db_schema.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index a0aa48fb76b13..ddd66a5bf04bd 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -154,7 +154,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="text" name="value" nullable="true" comment="Value"/> + <column xsi:type="mediumtext" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> @@ -408,7 +408,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="text" name="value" nullable="true" comment="Value"/> + <column xsi:type="mediumtext" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> diff --git a/app/code/Magento/Eav/etc/db_schema.xml b/app/code/Magento/Eav/etc/db_schema.xml index 5decc27ea8f26..a166e463f601c 100644 --- a/app/code/Magento/Eav/etc/db_schema.xml +++ b/app/code/Magento/Eav/etc/db_schema.xml @@ -205,7 +205,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="text" name="value" nullable="false" comment="Attribute Value"/> + <column xsi:type="mediumtext" name="value" nullable="false" comment="Attribute Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> From 5fb051baf0041ddc7fce70c370dc21cf323a969a Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 31 Jul 2020 13:22:20 -0500 Subject: [PATCH 16/95] MC-36227: Page builder content is getting cropped. --- app/code/Magento/Eav/etc/db_schema.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Eav/etc/db_schema.xml b/app/code/Magento/Eav/etc/db_schema.xml index a166e463f601c..5decc27ea8f26 100644 --- a/app/code/Magento/Eav/etc/db_schema.xml +++ b/app/code/Magento/Eav/etc/db_schema.xml @@ -205,7 +205,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="mediumtext" name="value" nullable="false" comment="Attribute Value"/> + <column xsi:type="text" name="value" nullable="false" comment="Attribute Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> From 5930f37aae6423c46442ee241f3e229e2766699c Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Tue, 4 Aug 2020 17:32:06 -0500 Subject: [PATCH 17/95] MC-36456: union implementation --- app/code/Magento/GraphQl/etc/di.xml | 4 + .../GraphQl/Config/Element/UnionFactory.php | 69 ++++++++++++ .../GraphQl/Config/Element/UnionInterface.php | 23 ++++ .../GraphQl/Config/Element/UnionType.php | 92 ++++++++++++++++ .../Output/ElementMapper/Formatter/Fields.php | 3 +- .../ElementMapper/Formatter/Interfaces.php | 4 +- .../ElementMapper/Formatter/ResolveType.php | 10 +- .../Output/ElementMapper/Formatter/Unions.php | 50 +++++++++ .../ElementMapper/FormatterComposite.php | 4 +- .../ElementMapper/FormatterInterface.php | 6 +- .../Schema/Type/Output/OutputMapper.php | 2 +- .../Schema/Type/Output/OutputUnionObject.php | 28 +++++ .../GraphQl/Schema/Type/UnionType.php | 16 +++ .../Framework/GraphQl/Schema/TypeFactory.php | 13 ++- .../GraphQlSchemaStitching/GraphQlReader.php | 15 ++- .../GraphQlReader/Reader/UnionType.php | 102 ++++++++++++++++++ 16 files changed, 424 insertions(+), 17 deletions(-) create mode 100644 lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php create mode 100644 lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php diff --git a/app/code/Magento/GraphQl/etc/di.xml b/app/code/Magento/GraphQl/etc/di.xml index fca6c425e2507..d6168cdc37600 100644 --- a/app/code/Magento/GraphQl/etc/di.xml +++ b/app/code/Magento/GraphQl/etc/di.xml @@ -29,6 +29,7 @@ <arguments> <argument name="factoryMapByConfigElementType" xsi:type="array"> <item name="graphql_interface" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InterfaceFactory</item> + <item name="graphql_union" xsi:type="object">Magento\Framework\GraphQl\Config\Element\UnionFactory</item> <item name="graphql_type" xsi:type="object">Magento\Framework\GraphQl\Config\Element\TypeFactory</item> <item name="graphql_input" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InputFactory</item> <item name="graphql_enum" xsi:type="object">Magento\Framework\GraphQl\Config\Element\EnumFactory</item> @@ -64,6 +65,7 @@ <item name="Magento\Framework\GraphQl\Config\Element\Type" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputTypeObject</item> <item name="Magento\Framework\GraphQl\Config\Element\Input" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Input\InputObjectType</item> <item name="Magento\Framework\GraphQl\Config\Element\InterfaceType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputInterfaceObject</item> + <item name="Magento\Framework\GraphQl\Config\Element\UnionType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputUnionObject</item> <item name="Magento\Framework\GraphQl\Config\Element\Enum" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Enum\Enum</item> </argument> </arguments> @@ -78,6 +80,7 @@ <argument name="formatters" xsi:type="array"> <item name="fields" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Fields</item> <item name="interfaces" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Interfaces</item> + <item name="unions" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Unions</item> <item name="resolveType" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\ResolveType</item> </argument> </arguments> @@ -85,6 +88,7 @@ <type name="Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeReaderComposite"> <arguments> <argument name="typeReaders" xsi:type="array"> + <item name="union_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\UnionType</item> <item name="enum_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\EnumType</item> <item name="object_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\ObjectType</item> <item name="input_object_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\InputObjectType</item> diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php new file mode 100644 index 0000000000000..e6dddd9535e82 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +use Magento\Framework\GraphQl\Config\ConfigElementFactoryInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\ObjectManagerInterface; + +/** + * Factory for config elements of 'union' type. + */ +class UnionFactory implements ConfigElementFactoryInterface +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * Instantiate an object representing 'interface' GraphQL config element. + * + * @param array $data + * @return ConfigElementInterface + */ + public function createFromConfigData(array $data): ConfigElementInterface + { + return $this->create($data, $data['types'] ?? []); + } + + /** + * Create interface object based off array of configured GraphQL Output/InputInterface. + * + * Interface data must contain name, type resolver, and field definitions. The type resolver should point to an + * implementation of the TypeResolverInterface that decides what concrete GraphQL type to output. Description is + * the only optional field. + * + * @param array $unionData + * @param array $types + * @return UnionType + */ + public function create( + array $unionData, + array $types + ) : UnionType { + return $this->objectManager->create( + UnionType::class, + [ + 'name' => $unionData['name'], + 'resolver' => $unionData['resolver'], + 'types' => $types, + 'description' => isset($unionData['description']) ? $unionData['description'] : '' + ] + ); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php new file mode 100644 index 0000000000000..85053325f193d --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +use Magento\Framework\GraphQl\Config\ConfigElementInterface; + +/** + * Defines contracts for return type data as GraphQL objects. + */ +interface UnionInterface extends ConfigElementInterface +{ + /** + * Get a list of fields that make up the possible return or input values of a type. + * + * @return Type[] + */ + public function getTypes() : array; +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php new file mode 100644 index 0000000000000..125baa344f1cd --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +/** + * Class representing 'union' GraphQL config element. + */ +class UnionType implements UnionInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var string[] + */ + private $types; + + /** + * @var string + */ + private $resolver; + + /** + * @var string + */ + private $description; + + /** + * @param string $name + * @param string $resolver + * @param string[] $types + * @param string $description + */ + public function __construct( + string $name, + string $resolver, + array $types, + string $description + ) { + $this->name = $name; + $this->types = $types; + $this->resolver = $resolver; + $this->description = $description; + } + + /** + * Get the type name. + * + * @return string + */ + public function getName() : string + { + return $this->name; + } + + /** + * Get a list of fields that make up the possible return or input values of a type. + * + * @return string[] + */ + public function getTypes() : array + { + return $this->types; + } + + /** + * Return the name of the resolver class that determines the concrete type to display in the result. + * + * @return string + */ + public function getResolver() + { + return $this->resolver; + } + + /** + * Get a human-readable description of the type. + * + * @return string + */ + public function getDescription() : string + { + return $this->description; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php index ad9fb675a6d70..7ff2ea60bd2cc 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQl\Config\Data\WrappedTypeProcessor; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\Input\InputMapper; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; @@ -89,7 +90,7 @@ public function __construct( /** * @inheritdoc */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType): array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { $typeConfig = [ 'fields' => function () use ($configElement, $outputType) { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php index 659c3f604508d..1f62384d181f4 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; use Magento\Framework\GraphQl\Config\Element\Type; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; @@ -34,7 +34,7 @@ public function __construct(OutputMapper $outputMapper) /** * {@inheritDoc} */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = []; if ($configElement instanceof Type && !empty($configElement->getInterfaces())) { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php index 3a40e609eb952..8af7953e399f5 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php @@ -8,7 +8,8 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; use Magento\Framework\GraphQl\Config\Element\InterfaceType; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\Element\UnionType; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\ObjectManagerInterface; @@ -34,7 +35,7 @@ public function __construct(ObjectManagerInterface $objectManager) /** * {@inheritDoc} */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = []; if ($configElement instanceof InterfaceType) { @@ -42,6 +43,11 @@ public function format(TypeInterface $configElement, OutputTypeInterface $output $config['resolveType'] = function ($value) use ($typeResolver) { return $typeResolver->resolveType($value); }; + } elseif ($configElement instanceof UnionType) { + $typeResolver = $this->objectManager->create($configElement->getResolver()); + $config['resolveType'] = function ($value) use ($typeResolver) { + return $typeResolver->resolveType($value); + }; } return $config; diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php new file mode 100644 index 0000000000000..450ce5ac7e494 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; + +use Magento\Framework\GraphQl\Config\Element\UnionType; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; +use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; +use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; + +/** + * Add unions implemented by type if configured. + */ +class Unions implements FormatterInterface +{ + /** + * @var OutputMapper + */ + private $outputMapper; + + /** + * @param OutputMapper $outputMapper + */ + public function __construct(OutputMapper $outputMapper) + { + $this->outputMapper = $outputMapper; + } + + /** + * {@inheritDoc} + */ + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array + { + $config = []; + if ($configElement instanceof UnionType && !empty($configElement->getTypes())) { + $unionTypes = []; + foreach ($configElement->getTypes() as $unionName) { + $unionTypes[$unionName] = $this->outputMapper->getOutputType($unionName); + } + $config['types'] = $unionTypes; + } + + return $config; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 416b4122b7097..83508722c339d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -7,7 +7,7 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; /** @@ -31,7 +31,7 @@ public function __construct(array $formatters) /** * {@inheritDoc} */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = [ 'name' => $configElement->getName(), diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php index 7d40b743a6a06..749a7690faaba 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php @@ -7,7 +7,7 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper; -use Magento\Framework\GraphQl\Config\Element\TypeInterface as TypeElementInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; /** @@ -18,9 +18,9 @@ interface FormatterInterface /** * Convert GraphQL config element to the object compatible with GraphQL schema generator. * - * @param TypeElementInterface $configElement + * @param ConfigElementInterface $configElement * @param OutputTypeInterface $outputType * @return array */ - public function format(TypeElementInterface $configElement, OutputTypeInterface $outputType) : array; + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php index 046eeb5b1f93d..f718cd4e9e04c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php @@ -38,7 +38,7 @@ public function __construct( * @return OutputTypeInterface * @throws GraphQlInputException */ - public function getOutputType($typeName) + public function getOutputType(string $typeName) { $outputType = $this->typeRegistry->get($typeName); diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php new file mode 100644 index 0000000000000..3c92004c33c90 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type\Output; + +use Magento\Framework\GraphQl\Config\Element\UnionType as UnionElement; +use Magento\Framework\GraphQl\Schema\Type\UnionType; + +/** + * 'union' type compatible with GraphQL schema generator. + */ +class OutputUnionObject extends UnionType +{ + /** + * @param ElementMapper $elementMapper + * @param UnionElement $configElement + */ + public function __construct( + ElementMapper $elementMapper, + UnionElement $configElement + ) { + parent::__construct($elementMapper->buildSchemaArray($configElement, $this)); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php new file mode 100644 index 0000000000000..a843c6c669acf --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type; + +/** + * Wrapper for GraphQl UnionType + */ +class UnionType extends \GraphQL\Type\Definition\UnionType implements OutputTypeInterface +{ + +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php b/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php index 3b31653382e3a..84f8d809c958e 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php @@ -13,7 +13,7 @@ use Magento\Framework\GraphQl\Schema\Type\EnumType; use Magento\Framework\GraphQl\Schema\Type\ListOfType; use Magento\Framework\GraphQl\Schema\Type\NonNull; -use Magento\Framework\GraphQl\Schema\TypeInterface; +use Magento\Framework\GraphQl\Schema\Type\UnionType; /** * Factory for @see TypeInterface implementations @@ -42,6 +42,17 @@ public function createInterface(array $config) : InterfaceType return new InterfaceType($config); } + /** + * Create an union type + * + * @param array $config + * @return UnionType + */ + public function createUnion(array $config) : UnionType + { + return new UnionType($config); + } + /** * Create an input object type * diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index 1e8b33f79854b..61209891fa08d 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -23,6 +23,8 @@ class GraphQlReader implements ReaderInterface public const GRAPHQL_INTERFACE = 'graphql_interface'; + public const GRAPHQL_UNION = 'graphql_union'; + /** * File locator * @@ -178,7 +180,7 @@ private function parseTypes(string $graphQlSchemaContent) : array private function copyInterfaceFieldsToConcreteTypes(array $source): array { foreach ($source as $interface) { - if ($interface['type'] == 'graphql_interface') { + if ($interface['type'] ?? '' == 'graphql_interface') { foreach ($source as $typeName => $type) { if (isset($type['implements']) && isset($type['implements'][$interface['name']]) @@ -253,7 +255,7 @@ private function convertInterfacesToAnnotations(string $graphQlSchemaContent): s private function addPlaceHolderInSchema(string $graphQlSchemaContent) :string { $placeholderField = self::GRAPHQL_PLACEHOLDER_FIELD_NAME; - $typesKindsPattern = '(type|interface|input)'; + $typesKindsPattern = '(type|interface|input|union)'; $enumKindsPattern = '(enum)'; $typeNamePattern = '([_A-Za-z][_0-9A-Za-z]+)'; $typeDefinitionPattern = '([^\{]*)(\{[\s\t\n\r^\}]*\})'; @@ -329,10 +331,13 @@ private static function getModuleNameForRelevantFile(string $file): string private function addModuleNameToTypes(array $source, string $filePath): array { foreach ($source as $typeName => $type) { - if (!isset($type['module']) && ( - ($type['type'] === self::GRAPHQL_INTERFACE && isset($type['typeResolver'])) + if (!isset($type['type'])) { + $x=1; + } + if ((!isset($type['module'])) + && (($type['type'] ?? '' === self::GRAPHQL_INTERFACE && isset($type['typeResolver'])) || isset($type['implements']) - ) + ) ) { $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php new file mode 100644 index 0000000000000..9dab871935dff --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader; + +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\FieldMetaReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheAnnotationReader; + +/** + * Composite configuration reader to handle the union object type meta + */ +class UnionType implements TypeMetaReaderInterface +{ + /** + * @var FieldMetaReader + */ + private $fieldMetaReader; + + /** + * @var DocReader + */ + private $docReader; + + /** + * @var CacheAnnotationReader + */ + private $cacheAnnotationReader; + + /** + * @param FieldMetaReader $fieldMetaReader + * @param DocReader $docReader + * @param CacheAnnotationReader|null $cacheAnnotationReader + */ + public function __construct( + FieldMetaReader $fieldMetaReader, + DocReader $docReader, + CacheAnnotationReader $cacheAnnotationReader = null + ) { + $this->fieldMetaReader = $fieldMetaReader; + $this->docReader = $docReader; + $this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheAnnotationReader::class); + } + + /** + * @inheritdoc + */ + public function read(\GraphQL\Type\Definition\Type $typeMeta) : array + { + if ($typeMeta instanceof \GraphQL\Type\Definition\UnionType) { + $typeName = $typeMeta->name; + $types = $typeMeta->getTypes(); + $result = [ + 'name' => $typeName, + 'type' => 'graphql_union', + 'types' => $types, + ]; + + $unionResolveType = $this->getUnionTypeResolver($typeMeta); + if (!empty($unionResolveType)) { + $result['resolver'] = $unionResolveType; + } + + + if ($this->docReader->read($typeMeta->astNode->directives)) { + $result['description'] = $this->docReader->read($typeMeta->astNode->directives); + } + + return $result; + } else { + return []; + } + } + + /** + * Retrieve the interface type resolver if it exists from the meta data + * + * @param \GraphQL\Type\Definition\UnionType $unionTypeMeta + * @return string + */ + private function getUnionTypeResolver(\GraphQL\Type\Definition\UnionType $unionTypeMeta) : string + { + /** @var \GraphQL\Language\AST\NodeList $directives */ + $directives = $unionTypeMeta->astNode->directives; + foreach ($directives as $directive) { + if ($directive->name->value == 'resolver') { + foreach ($directive->arguments as $directiveArgument) { + if ($directiveArgument->name->value == 'class') { + return $directiveArgument->value->value; + } + } + } + } + return ''; + } +} From 9625a3443ce5cfe7576fa35a3969bf160798a784 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 13 Aug 2020 10:40:10 -0500 Subject: [PATCH 18/95] MC-36456: union implementation --- app/code/Magento/GraphQl/etc/schema.graphqls | 4 +--- .../GraphQl/Config/Element/UnionFactory.php | 2 +- .../Framework/GraphQl/Config/Element/UnionType.php | 12 ++++++------ .../Output/ElementMapper/Formatter/ResolveType.php | 2 +- .../GraphQlSchemaStitching/GraphQlReader.php | 3 --- .../GraphQlReader/Reader/UnionType.php | 4 ++-- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 0212d32db0f2f..035ec98fa06e6 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -30,14 +30,12 @@ directive @resolver(class: String="") on QUERY | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION - | INTERFACE - | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION -directive @typeResolver(class: String="") on INTERFACE | OBJECT +directive @typeResolver(class: String="") on INTERFACE | OBJECT | UNION directive @cache(cacheIdentity: String="" cacheable: Boolean=true) on QUERY diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php index e6dddd9535e82..d26538b576fd3 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php @@ -60,7 +60,7 @@ public function create( UnionType::class, [ 'name' => $unionData['name'], - 'resolver' => $unionData['resolver'], + 'typeResolver' => $unionData['typeResolver'], 'types' => $types, 'description' => isset($unionData['description']) ? $unionData['description'] : '' ] diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php index 125baa344f1cd..d386a59a9ec6a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php @@ -25,7 +25,7 @@ class UnionType implements UnionInterface /** * @var string */ - private $resolver; + private $typeResolver; /** * @var string @@ -34,19 +34,19 @@ class UnionType implements UnionInterface /** * @param string $name - * @param string $resolver + * @param string $typeResolver * @param string[] $types * @param string $description */ public function __construct( string $name, - string $resolver, + string $typeResolver, array $types, string $description ) { $this->name = $name; $this->types = $types; - $this->resolver = $resolver; + $this->typeResolver = $typeResolver; $this->description = $description; } @@ -75,9 +75,9 @@ public function getTypes() : array * * @return string */ - public function getResolver() + public function getTypeResolver() { - return $this->resolver; + return $this->typeResolver; } /** diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php index 8af7953e399f5..529cdf4467f1d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php @@ -44,7 +44,7 @@ public function format(ConfigElementInterface $configElement, OutputTypeInterfac return $typeResolver->resolveType($value); }; } elseif ($configElement instanceof UnionType) { - $typeResolver = $this->objectManager->create($configElement->getResolver()); + $typeResolver = $this->objectManager->create($configElement->getTypeResolver()); $config['resolveType'] = function ($value) use ($typeResolver) { return $typeResolver->resolveType($value); }; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index 61209891fa08d..6d3452b009815 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -331,9 +331,6 @@ private static function getModuleNameForRelevantFile(string $file): string private function addModuleNameToTypes(array $source, string $filePath): array { foreach ($source as $typeName => $type) { - if (!isset($type['type'])) { - $x=1; - } if ((!isset($type['module'])) && (($type['type'] ?? '' === self::GRAPHQL_INTERFACE && isset($type['typeResolver'])) || isset($type['implements']) diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php index 9dab871935dff..d046b346aa4c1 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -64,7 +64,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $unionResolveType = $this->getUnionTypeResolver($typeMeta); if (!empty($unionResolveType)) { - $result['resolver'] = $unionResolveType; + $result['typeResolver'] = $unionResolveType; } @@ -89,7 +89,7 @@ private function getUnionTypeResolver(\GraphQL\Type\Definition\UnionType $unionT /** @var \GraphQL\Language\AST\NodeList $directives */ $directives = $unionTypeMeta->astNode->directives; foreach ($directives as $directive) { - if ($directive->name->value == 'resolver') { + if ($directive->name->value == 'typeResolver') { foreach ($directive->arguments as $directiveArgument) { if ($directiveArgument->name->value == 'class') { return $directiveArgument->value->value; From 24b3dfe36d2ddea661c8f8e56d3b7195ef321edb Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 13 Aug 2020 13:19:22 -0500 Subject: [PATCH 19/95] MC-36456: union implementation --- app/code/Magento/GraphQl/etc/schema.graphqls | 4 +++- .../Argument/SearchCriteria/ArgumentApplier/Filter.php | 2 +- .../Argument/SearchCriteria/ArgumentApplier/Sort.php | 2 +- .../Type/Output/ElementMapper/Formatter/Interfaces.php | 2 +- .../Type/Output/ElementMapper/Formatter/ResolveType.php | 2 +- .../Schema/Type/Output/ElementMapper/Formatter/Unions.php | 2 +- .../Type/Output/ElementMapper/FormatterComposite.php | 4 ++-- .../Framework/GraphQlSchemaStitching/GraphQlReader.php | 2 +- .../GraphQlReader/Reader/EnumType.php | 2 +- .../GraphQlReader/Reader/InputObjectType.php | 2 +- .../GraphQlReader/Reader/InterfaceType.php | 2 +- .../GraphQlReader/Reader/ObjectType.php | 2 +- .../GraphQlReader/Reader/UnionType.php | 8 ++++---- .../GraphQlReader/TypeReaderComposite.php | 2 +- 14 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 035ec98fa06e6..39596b9d00599 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -35,7 +35,9 @@ directive @resolver(class: String="") on QUERY | INPUT_OBJECT | INPUT_FIELD_DEFINITION -directive @typeResolver(class: String="") on INTERFACE | OBJECT | UNION +directive @typeResolver(class: String="") on UNION + | INTERFACE + | OBJECT directive @cache(cacheIdentity: String="" cacheable: Boolean=true) on QUERY diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php index 0e5feeba3bade..5ec80b1d4692c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php @@ -51,7 +51,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function applyArgument( SearchCriteriaInterface $searchCriteria, diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php index 6ecb1896d685a..d312837e41686 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php @@ -33,7 +33,7 @@ public function __construct(SortOrderBuilder $sortOrderBuilder = null) } /** - * {@inheritdoc} + * @inheritDoc */ public function applyArgument( SearchCriteriaInterface $searchCriteria, diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php index 1f62384d181f4..761ba68e44521 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php @@ -32,7 +32,7 @@ public function __construct(OutputMapper $outputMapper) } /** - * {@inheritDoc} + * @inheritDoc */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php index 529cdf4467f1d..8077052d7dd1f 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php @@ -33,7 +33,7 @@ public function __construct(ObjectManagerInterface $objectManager) } /** - * {@inheritDoc} + * @inheritDoc */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php index 450ce5ac7e494..e64c45168f4c7 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php @@ -32,7 +32,7 @@ public function __construct(OutputMapper $outputMapper) } /** - * {@inheritDoc} + * @inheritDoc */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 83508722c339d..5be5f8760c207 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -11,7 +11,7 @@ use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; /** - * {@inheritdoc} + * @inheritDoc */ class FormatterComposite implements FormatterInterface { @@ -29,7 +29,7 @@ public function __construct(array $formatters) } /** - * {@inheritDoc} + * @inheritDoc */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index 6d3452b009815..d29777321d77c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -71,7 +71,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc * * @param string|null $scope * @return array diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index e4dec7afdab0a..a0da1038efb0f 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -30,7 +30,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php index 38159fac03b3b..9e79b4025234b 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php @@ -49,7 +49,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php index baadb4be61cf2..c1c716632d90b 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php @@ -49,7 +49,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php index ba8e46dd60557..e174cfd954394 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php @@ -69,7 +69,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php index d046b346aa4c1..5da107b12e6c2 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -49,7 +49,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -62,9 +62,9 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array 'types' => $types, ]; - $unionResolveType = $this->getUnionTypeResolver($typeMeta); - if (!empty($unionResolveType)) { - $result['typeResolver'] = $unionResolveType; + $unionTypeResolver = $this->getUnionTypeResolver($typeMeta); + if (!empty($unionTypeResolver)) { + $result['typeResolver'] = $unionTypeResolver; } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php index e9b899bb2bb5b..614c1e3a743a0 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php @@ -25,7 +25,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { From 19ba821dedb77b682ecd3b72ef4385496a4f5c2f Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 13 Aug 2020 16:15:41 -0500 Subject: [PATCH 20/95] MC-36456: union implementation --- .../Framework/GraphQl/Config/Element/UnionFactory.php | 10 +++++----- .../GraphQl/Config/Element/UnionInterface.php | 2 +- .../Output/ElementMapper/Formatter/ResolveType.php | 7 +------ .../Type/Output/ElementMapper/FormatterComposite.php | 7 ++++--- .../GraphQl/Schema/Type/Output/OutputMapper.php | 2 +- .../GraphQl/Schema/Type/Output/OutputUnionObject.php | 2 +- .../Framework/GraphQlSchemaStitching/GraphQlReader.php | 8 ++++---- .../GraphQlReader/Reader/EnumType.php | 4 +++- .../GraphQlReader/Reader/InputObjectType.php | 4 +++- .../GraphQlReader/Reader/InterfaceType.php | 4 +++- .../GraphQlReader/Reader/ObjectType.php | 4 +++- .../GraphQlReader/Reader/UnionType.php | 7 ++++--- 12 files changed, 33 insertions(+), 28 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php index d26538b576fd3..02d0a60064db6 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php @@ -31,7 +31,7 @@ public function __construct( } /** - * Instantiate an object representing 'interface' GraphQL config element. + * Instantiate an object representing 'union' GraphQL config element. * * @param array $data * @return ConfigElementInterface @@ -42,11 +42,11 @@ public function createFromConfigData(array $data): ConfigElementInterface } /** - * Create interface object based off array of configured GraphQL Output/InputInterface. + * Create union object based off array of configured GraphQL. * - * Interface data must contain name, type resolver, and field definitions. The type resolver should point to an - * implementation of the TypeResolverInterface that decides what concrete GraphQL type to output. Description is - * the only optional field. + * Union data must contain name, type resolver, and possible concrete types definitions + * The type resolver should point to an implementation of the TypeResolverInterface + * that decides what concrete GraphQL type to output. Description is the only optional field. * * @param array $unionData * @param array $types diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php index 85053325f193d..f5d0f68b4b8c8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php @@ -10,7 +10,7 @@ use Magento\Framework\GraphQl\Config\ConfigElementInterface; /** - * Defines contracts for return type data as GraphQL objects. + * Defines the contract for the union configuration data type. */ interface UnionInterface extends ConfigElementInterface { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php index 8077052d7dd1f..553e8fe40efc2 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php @@ -38,12 +38,7 @@ public function __construct(ObjectManagerInterface $objectManager) public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = []; - if ($configElement instanceof InterfaceType) { - $typeResolver = $this->objectManager->create($configElement->getTypeResolver()); - $config['resolveType'] = function ($value) use ($typeResolver) { - return $typeResolver->resolveType($value); - }; - } elseif ($configElement instanceof UnionType) { + if ($configElement instanceof InterfaceType || $configElement instanceof UnionType) { $typeResolver = $this->objectManager->create($configElement->getTypeResolver()); $config['resolveType'] = function ($value) use ($typeResolver) { return $typeResolver->resolveType($value); diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 5be5f8760c207..74ea1467939f4 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -33,14 +33,15 @@ public function __construct(array $formatters) */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { - $config = [ + $defaultConfig = [ 'name' => $configElement->getName(), 'description' => $configElement->getDescription() ]; + $formattedConfig = []; foreach ($this->formatters as $formatter) { - $config = array_merge($config, $formatter->format($configElement, $outputType)); + $formattedConfig[] = $formatter->format($configElement, $outputType); } - return $config; + return array_merge($defaultConfig,...$formattedConfig); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php index f718cd4e9e04c..afe0d84de26f8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php @@ -13,7 +13,7 @@ use Magento\Framework\Phrase; /** - * Map type names to their output type/interface/enum classes. + * Map type names to their output type/interface/union/enum classes. */ class OutputMapper { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php index 3c92004c33c90..a63b7b7417527 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php @@ -11,7 +11,7 @@ use Magento\Framework\GraphQl\Schema\Type\UnionType; /** - * 'union' type compatible with GraphQL schema generator. + * The 'union' type compatible with GraphQL schema generator. */ class OutputUnionObject extends UnionType { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index d29777321d77c..34913ae949f28 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -11,6 +11,7 @@ use Magento\Framework\Config\FileResolverInterface; use Magento\Framework\Config\ReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface as TypeReaderComposite; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\InterfaceType; /** * Reads *.graphqls files from modules and combines the results as array to be used with a library to configure objects @@ -21,10 +22,9 @@ class GraphQlReader implements ReaderInterface public const GRAPHQL_SCHEMA_FILE = 'schema.graphqls'; + /** @deprecated */ public const GRAPHQL_INTERFACE = 'graphql_interface'; - public const GRAPHQL_UNION = 'graphql_union'; - /** * File locator * @@ -180,7 +180,7 @@ private function parseTypes(string $graphQlSchemaContent) : array private function copyInterfaceFieldsToConcreteTypes(array $source): array { foreach ($source as $interface) { - if ($interface['type'] ?? '' == 'graphql_interface') { + if ($interface['type'] ?? '' == InterfaceType::GRAPHQL_INTERFACE) { foreach ($source as $typeName => $type) { if (isset($type['implements']) && isset($type['implements'][$interface['name']]) @@ -332,7 +332,7 @@ private function addModuleNameToTypes(array $source, string $filePath): array { foreach ($source as $typeName => $type) { if ((!isset($type['module'])) - && (($type['type'] ?? '' === self::GRAPHQL_INTERFACE && isset($type['typeResolver'])) + && (($type['type'] ?? '' === InterfaceType::GRAPHQL_INTERFACE && isset($type['typeResolver'])) || isset($type['implements']) ) ) { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index a0da1038efb0f..c6e0481c7490e 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -15,6 +15,8 @@ */ class EnumType implements TypeMetaReaderInterface { + public const GRAPHQL_ENUM = 'graphql_enum'; + /** * @var DocReader */ @@ -37,7 +39,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array if ($typeMeta instanceof \GraphQL\Type\Definition\EnumType) { $result = [ 'name' => $typeMeta->name, - 'type' => 'graphql_enum', + 'type' => self::GRAPHQL_ENUM, 'items' => [] // Populated later ]; foreach ($typeMeta->getValues() as $enumValueMeta) { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php index 9e79b4025234b..2108a04d8a9ed 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php @@ -17,6 +17,8 @@ */ class InputObjectType implements TypeMetaReaderInterface { + public const GRAPHQL_INPUT = 'graphql_input'; + /** * @var TypeMetaWrapperReader */ @@ -57,7 +59,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_input', + 'type' => self::GRAPHQL_INPUT, 'fields' => [] // Populated later ]; $fields = $typeMeta->getFields(); diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php index c1c716632d90b..76550469d409e 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php @@ -17,6 +17,8 @@ */ class InterfaceType implements TypeMetaReaderInterface { + public const GRAPHQL_INTERFACE = 'graphql_interface'; + /** * @var FieldMetaReader */ @@ -57,7 +59,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_interface', + 'type' => self::GRAPHQL_INTERFACE, 'fields' => [] ]; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php index e174cfd954394..3ad6d69eb5c9c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php @@ -19,6 +19,8 @@ */ class ObjectType implements TypeMetaReaderInterface { + public const GRAPHQL_TYPE = 'graphql_type'; + /** * @var FieldMetaReader */ @@ -77,7 +79,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_type', + 'type' => self::GRAPHQL_TYPE, 'fields' => [], // Populated later ]; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php index 5da107b12e6c2..8f4ce8f94aba6 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -17,6 +17,8 @@ */ class UnionType implements TypeMetaReaderInterface { + public const GRAPHQL_UNION = 'graphql_union'; + /** * @var FieldMetaReader */ @@ -58,7 +60,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $types = $typeMeta->getTypes(); $result = [ 'name' => $typeName, - 'type' => 'graphql_union', + 'type' => self::GRAPHQL_UNION, 'types' => $types, ]; @@ -67,7 +69,6 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $result['typeResolver'] = $unionTypeResolver; } - if ($this->docReader->read($typeMeta->astNode->directives)) { $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } @@ -79,7 +80,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array } /** - * Retrieve the interface type resolver if it exists from the meta data + * Retrieve the union type resolver if it exists from the meta data * * @param \GraphQL\Type\Definition\UnionType $unionTypeMeta * @return string From c531be91fe2a19040564cff7b640df798bffbfdf Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 13 Aug 2020 17:48:38 -0500 Subject: [PATCH 21/95] MC-36456: union implementation --- .../Framework/GraphQl/Config/Element/UnionInterface.php | 2 +- .../Framework/GraphQl/Config/Element/UnionType.php | 8 ++++---- .../Schema/Type/Output/ElementMapper/Formatter/Unions.php | 2 +- .../Type/Output/ElementMapper/FormatterComposite.php | 4 ++-- .../Type/Output/ElementMapper/FormatterInterface.php | 2 +- .../GraphQlReader/Reader/UnionType.php | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php index f5d0f68b4b8c8..2d557e6dc5b84 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php @@ -19,5 +19,5 @@ interface UnionInterface extends ConfigElementInterface * * @return Type[] */ - public function getTypes() : array; + public function getTypes(): array; } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php index d386a59a9ec6a..5b27d03360efc 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php @@ -55,7 +55,7 @@ public function __construct( * * @return string */ - public function getName() : string + public function getName(): string { return $this->name; } @@ -65,7 +65,7 @@ public function getName() : string * * @return string[] */ - public function getTypes() : array + public function getTypes(): array { return $this->types; } @@ -75,7 +75,7 @@ public function getTypes() : array * * @return string */ - public function getTypeResolver() + public function getTypeResolver(): string { return $this->typeResolver; } @@ -85,7 +85,7 @@ public function getTypeResolver() * * @return string */ - public function getDescription() : string + public function getDescription(): string { return $this->description; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php index e64c45168f4c7..75b6a58790a09 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php @@ -34,7 +34,7 @@ public function __construct(OutputMapper $outputMapper) /** * @inheritDoc */ - public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { $config = []; if ($configElement instanceof UnionType && !empty($configElement->getTypes())) { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 74ea1467939f4..99ef725afe32f 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -31,7 +31,7 @@ public function __construct(array $formatters) /** * @inheritDoc */ - public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { $defaultConfig = [ 'name' => $configElement->getName(), @@ -42,6 +42,6 @@ public function format(ConfigElementInterface $configElement, OutputTypeInterfac $formattedConfig[] = $formatter->format($configElement, $outputType); } - return array_merge($defaultConfig,...$formattedConfig); + return array_merge($defaultConfig, ...$formattedConfig); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php index 749a7690faaba..a99237aa8843a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php @@ -22,5 +22,5 @@ interface FormatterInterface * @param OutputTypeInterface $outputType * @return array */ - public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array; + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array; } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php index 8f4ce8f94aba6..4280ccd17adb4 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -53,7 +53,7 @@ public function __construct( /** * @inheritDoc */ - public function read(\GraphQL\Type\Definition\Type $typeMeta) : array + public function read(\GraphQL\Type\Definition\Type $typeMeta): array { if ($typeMeta instanceof \GraphQL\Type\Definition\UnionType) { $typeName = $typeMeta->name; @@ -85,7 +85,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array * @param \GraphQL\Type\Definition\UnionType $unionTypeMeta * @return string */ - private function getUnionTypeResolver(\GraphQL\Type\Definition\UnionType $unionTypeMeta) : string + private function getUnionTypeResolver(\GraphQL\Type\Definition\UnionType $unionTypeMeta): string { /** @var \GraphQL\Language\AST\NodeList $directives */ $directives = $unionTypeMeta->astNode->directives; From 2d58988451bd736066e24c7be7f42508e753791f Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Fri, 14 Aug 2020 16:55:36 +0300 Subject: [PATCH 22/95] MFTF update. --- ...frontAccountDownloadableProductLinkAfterPartialRefundTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml index eaff4a5b116c3..d82cc25b0eccf 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -51,6 +51,7 @@ <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> </after> From 35c9d62fecc66372c98b0dc5e2914928eab8510f Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 14 Aug 2020 13:38:21 -0500 Subject: [PATCH 23/95] MC-36456: union implementation --- .../Model/Resolver/Item.php | 3 ++ .../Model/Resolver/UnionType1.php | 49 +++++++++++++++++++ .../Model/Resolver/UnionTypeResolver.php | 28 +++++++++++ .../etc/schema.graphqls | 12 +++++ .../GraphQl/TestModule/GraphQlQueryTest.php | 47 ++++++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php index 1731a974aaed3..71ff93875f2c1 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php @@ -11,6 +11,9 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; +/** + * Resolver for Item + */ class Item implements ResolverInterface { /** diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php new file mode 100644 index 0000000000000..e012178d0ce22 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleGraphQlQuery\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface; + +/** + * Resolver for Union Type 1 + */ +class UnionType1 implements ResolverInterface +{ + /** + * @var CollectionFactoryInterface + */ + private $collectionFactory; + + /** + * @param CollectionFactoryInterface $collectionFactory + */ + public function __construct( + CollectionFactoryInterface $collectionFactory + ) { + $this->collectionFactory = $collectionFactory; + } + + /** + * @inheritDoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + return [ + 'custom_name1' => 'custom_name1_value', + 'custom_name2' => 'custom_name2_value', + ]; + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php new file mode 100644 index 0000000000000..5264adc89e9f9 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleGraphQlQuery\Model\Resolver; + +use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; + +/** + * Type Resolver for union + */ +class UnionTypeResolver implements TypeResolverInterface +{ + /** + * @inheritDoc + */ + public function resolveType(array $data): string + { + if (!empty($data)) { + return 'TypeCustom1'; + } + return ''; + } + +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls index 7eb175a88e322..09050085bc0a2 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls @@ -3,6 +3,7 @@ type Query { testItem(id: Int!) : Item @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\Item") + testUnion: UnionType1 @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionType1") } type Mutation { @@ -18,3 +19,14 @@ type MutationItem { item_id: Int name: String } + +union UnionType1 @doc(description: "some kind of union") @typeResolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionTypeResolver") = + TypeCustom1 | TypeCustom2 + +type TypeCustom1 { + custom_name1: String +} + +type TypeCustom2 { + custom_name2: String +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index 2db06e383758f..204493fcef107 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -25,6 +25,15 @@ public function testQueryTestModuleReturnsResults() item_id name } + testUnion { + __typename + ... on TypeCustom1 { + custom_name1 + } + ... on TypeCustom2 { + custom_name2 + } + } } QUERY; @@ -100,4 +109,42 @@ public function testQueryViaGetRequestWithVariablesReturnsResults() $this->assertArrayHasKey('testItem', $response); } + + public function testQueryTestUnionResults() + { + $id = 1; + + $query = <<<QUERY +{ + testItem(id: {$id}) + { + item_id + name + } + testUnion { + __typename + ... on TypeCustom1 { + custom_name1 + } + ... on TypeCustom2 { + custom_name2 + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('testItem', $response); + $testItem = $response['testItem']; + $this->assertArrayHasKey('item_id', $testItem); + $this->assertArrayHasKey('name', $testItem); + $this->assertEquals(1, $testItem['item_id']); + $this->assertEquals('itemName', $testItem['name']); + + $this->assertArrayHasKey('testUnion', $response); + $testUnion = $response['testUnion']; + $this->assertArrayHasKey('custom_name1', $testUnion); + $this->assertEquals('custom_name1_value', $testUnion['custom_name1']); + $this->assertArrayNotHasKey('custom_name2', $testUnion); + } } From 7a2dd87ce807c7f82e053697ae5fe8508ba8f83c Mon Sep 17 00:00:00 2001 From: Victor Rad <vrad@magento.com> Date: Mon, 17 Aug 2020 11:27:14 -0500 Subject: [PATCH 24/95] MC-36598: Default Billing address is not selected after the same address checkbox unticked --- .../view/frontend/web/js/view/billing-address/list.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js index ca3a267c01671..80411fb8eb29d 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js @@ -23,6 +23,9 @@ define([ }, addressOptions = addressList().filter(function (address) { return address.getType() === 'customer-address'; + }), + addressDefaultIndex = addressOptions.findIndex(function (address) { + return address.isDefaultBilling(); }); return Component.extend({ @@ -53,7 +56,8 @@ define([ this._super() .observe('selectedAddress isNewAddressSelected') .observe({ - isNewAddressSelected: !customer.isLoggedIn() || !addressOptions.length + isNewAddressSelected: !customer.isLoggedIn() || !addressOptions.length, + selectedAddress: this.addressOptions[addressDefaultIndex] }); return this; From 7ca421d8231ae7b77e857861e8bd9cda7dd77b5f Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 17 Aug 2020 13:48:31 -0500 Subject: [PATCH 25/95] MC-36456: union implementation --- .../Model/Resolver/UnionTypeResolver.php | 1 - .../Magento/GraphQl/TestModule/GraphQlQueryTest.php | 11 +---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php index 5264adc89e9f9..40cbdadb8a948 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php @@ -24,5 +24,4 @@ public function resolveType(array $data): string } return ''; } - } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index 204493fcef107..7d2d6219e0272 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -10,7 +10,7 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; /** - * Class GraphQlQueryTest + * Test for basic GraphQl features */ class GraphQlQueryTest extends GraphQlAbstract { @@ -25,15 +25,6 @@ public function testQueryTestModuleReturnsResults() item_id name } - testUnion { - __typename - ... on TypeCustom1 { - custom_name1 - } - ... on TypeCustom2 { - custom_name2 - } - } } QUERY; From 5343a452f8c2c1f64176489df831dbb2511d16f9 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 17 Aug 2020 19:34:21 -0500 Subject: [PATCH 26/95] MC-36456: union implementation --- .../Magento/Framework/GraphQl/Config/Element/Type.php | 10 +++++++++- .../GraphQlReader/Reader/UnionType.php | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php index 20d017cc71062..073fb2e795e87 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php @@ -73,7 +73,15 @@ public function getFields() : array /** * Get interfaces the type implements, if any. Return an empty array if none are configured. * - * @return string[] + * Example return array( + * array( + * 'interface' => 'SomeDefinedTypeInterface', + * 'copyFields' => true + * ), + * ... + * ), + * + * @return array */ public function getInterfaces() : array { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php index 4280ccd17adb4..5776901765b5a 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -57,11 +57,10 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta): array { if ($typeMeta instanceof \GraphQL\Type\Definition\UnionType) { $typeName = $typeMeta->name; - $types = $typeMeta->getTypes(); $result = [ 'name' => $typeName, 'type' => self::GRAPHQL_UNION, - 'types' => $types, + 'types' => [], ]; $unionTypeResolver = $this->getUnionTypeResolver($typeMeta); @@ -69,6 +68,10 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta): array $result['typeResolver'] = $unionTypeResolver; } + foreach ($typeMeta->getTypes() as $type) { + $result['types'][] = $type->name; + } + if ($this->docReader->read($typeMeta->astNode->directives)) { $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } From f54fd865772834c402bebfe204dba234f6a0fba7 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Tue, 18 Aug 2020 16:15:05 +0300 Subject: [PATCH 27/95] added some action groups to analytics module --- ...figGeneralAnalyticsPageOpenActionGroup.xml | 19 ++++++++++++++++++ ...minAdvancedReportingPageUrlActionGroup.xml | 20 +++++++++++++++++++ .../Test/AdminAdvancedReportingButtonTest.xml | 3 +-- ...AdminAdvancedReportingNavigateMenuTest.xml | 4 +--- .../AdminConfigurationBlankIndustryTest.xml | 6 ++++-- ...onfigurationEnableDisableAnalyticsTest.xml | 6 ++++-- .../Test/AdminConfigurationIndustryTest.xml | 10 +++++++--- .../AdminConfigurationTimeToSendDataTest.xml | 6 ++++-- 8 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml create mode 100644 app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml new file mode 100644 index 0000000000000..713409e2b9a78 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml @@ -0,0 +1,19 @@ +<?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="AdminConfigGeneralAnalyticsPageOpenActionGroup"> + <annotations> + <description>Open Config General Analytics Page.</description> + </annotations> + + <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <waitForPageLoad stepKey="waitPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml new file mode 100644 index 0000000000000..51d77228c8dcf --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.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="AssertAdminAdvancedReportingPageUrlActionGroup"> + <annotations> + <description>Assert admin advanced reporting page url.</description> + </annotations> + + <switchToNextTab stepKey="switchToNewTab"/> + <waitForPageLoad stepKey="waitForAdvancedReportingPageLoad"/> + <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml index cbcbb3a5dd64c..9c99041be0df6 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml @@ -32,7 +32,6 @@ <amOnPage url="{{AdminDashboardPage.url}}" stepKey="amOnDashboardPage"/> <waitForPageLoad stepKey="waitForDashboardPageLoad"/> <click selector="{{AdminAdvancedReportingSection.goToAdvancedReporting}}" stepKey="clickGoToAdvancedReporting"/> - <switchToNextTab stepKey="switchToNewTab"/> - <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + <actionGroup ref="AssertAdminAdvancedReportingPageUrlActionGroup" stepKey="assertAdvancedReportingPageUrl"/> </test> </tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml index ee25e80fcab30..f350452cfc7d0 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml @@ -29,8 +29,6 @@ <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> <argument name="submenuUiId" value="{{AdminMenuReportsBusinessIntelligenceAdvancedReporting.dataUiId}}"/> </actionGroup> - <switchToNextTab stepKey="switchToNewTab"/> - <waitForPageLoad stepKey="waitForAdvancedReportingPageLoad"/> - <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + <actionGroup ref="AssertAdminAdvancedReportingPageUrlActionGroup" stepKey="assertAdvancedReportingPageUrl"/> </test> </tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml index 17d463030d91c..3f960333f4dcb 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml @@ -17,11 +17,13 @@ <testCaseId value="MAGETWO-63981"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="--Please Select--" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml index b03488c240604..35f9e95885c0d 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -17,11 +17,13 @@ <testCaseId value="MAGETWO-66465"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingServiceLabel}}" userInput="Advanced Reporting Service" stepKey="seeAdvancedReportingServiceLabelEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml index c19fddc6aa0ce..a6cc8d7853cbe 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -18,9 +18,13 @@ <testCaseId value="MAGETWO-63898"/> <group value="analytics"/> </annotations> - - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </after> + <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml index 6231b17c17b02..020f2cf03974a 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -18,11 +18,13 @@ <testCaseId value="MAGETWO-66464"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingHour}}" userInput="23" stepKey="selectAdvancedReportingHour"/> From 8ecdbcf91f62315abe9db041b32e4fd76aa71f71 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 18 Aug 2020 12:50:33 -0500 Subject: [PATCH 28/95] MC-36227: Page builder content is getting cropped. --- .../Magento/Catalog/Model/CategoryTest.php | 25 ++++ .../Magento/Catalog/Model/ProductTest.php | 107 +++++++++++++----- 2 files changed, 105 insertions(+), 27 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php index 0d2f9d63c5d7f..8c25a82e0f6fd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php @@ -15,6 +15,8 @@ use Magento\Catalog\Model\ResourceModel\Category\Tree; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Math\Random; use Magento\Framework\Url; use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\Store; @@ -419,6 +421,29 @@ public function testCategoryCreateWithDifferentFields(array $data): void $this->assertSame($data, $categoryData); } + /** + * Test for Category Description field to be able to contain >64kb of data + * + * @throws NoSuchEntityException + * @throws \Exception + */ + public function testMaximumDescriptionLength(): void + { + $random = Bootstrap::getObjectManager()->get(Random::class); + $longDescription = $random->getRandomString(70000); + + $requiredData = [ + 'name' => 'Test Category', + 'attribute_set_id' => '3', + 'parent_id' => 2, + 'description' => $longDescription + ]; + $this->_model->setData($requiredData); + $this->categoryResource->save($this->_model); + $category = $this->categoryRepository->get($this->_model->getId()); + $this->assertEquals($longDescription, $category->getDescription()); + } + /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index b56e9e502cce6..b0f36f250991b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -8,14 +8,19 @@ namespace Magento\Catalog\Model; -use Magento\Eav\Model\Config as EavConfig; -use Magento\Catalog\Model\Product; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\TestFramework\ObjectManager; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use Magento\Framework\Math\Random; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; /** * Tests product model: @@ -119,14 +124,62 @@ public function testCRUD() )->setMetaDescription( 'meta description' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED ); $crud = new \Magento\TestFramework\Entity($this->_model, ['sku' => uniqid()]); $crud->testCrud(); } + /** + * Test for Product Description field to be able to contain >64kb of data + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoAppArea adminhtml + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + * @throws LocalizedException + */ + public function testMaximumDescriptionLength() + { + $sku = uniqid(); + $random = Bootstrap::getObjectManager()->get(Random::class); + $longDescription = $random->getRandomString(70000); + + $this->_model->setTypeId( + 'simple' + )->setAttributeSetId( + 4 + )->setName( + 'Simple Product With Long Description' + )->setDescription( + $longDescription + )->setSku( + $sku + )->setPrice( + 10 + )->setMetaTitle( + 'meta title' + )->setMetaKeyword( + 'meta keyword' + )->setMetaDescription( + 'meta description' + )->setVisibility( + Visibility::VISIBILITY_BOTH + )->setStatus( + Status::STATUS_ENABLED + ); + + $this->productRepository->save($this->_model); + $product = $this->productRepository->get($sku); + + $this->assertEquals($longDescription, $product->getDescription()); + } + /** * Test clean cache * @@ -219,7 +272,7 @@ public function testDuplicate() $this->assertNotEquals($duplicate->getId(), $this->_model->getId()); $this->assertNotEquals($duplicate->getSku(), $this->_model->getSku()); $this->assertEquals( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED, + Status::STATUS_DISABLED, $duplicate->getStatus() ); $this->assertEquals(\Magento\Store\Model\Store::DEFAULT_STORE_ID, $duplicate->getStoreId()); @@ -275,35 +328,35 @@ protected function _undo($duplicate) public function testVisibilityApi() { $this->assertEquals( - [\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED], + [Status::STATUS_ENABLED], $this->_model->getVisibleInCatalogStatuses() ); $this->assertEquals( - [\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED], + [Status::STATUS_ENABLED], $this->_model->getVisibleStatuses() ); - $this->_model->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED); + $this->_model->setStatus(Status::STATUS_DISABLED); $this->assertFalse($this->_model->isVisibleInCatalog()); - $this->_model->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + $this->_model->setStatus(Status::STATUS_ENABLED); $this->assertTrue($this->_model->isVisibleInCatalog()); $this->assertEquals( [ - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_SEARCH, - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG, - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH, + Visibility::VISIBILITY_IN_SEARCH, + Visibility::VISIBILITY_IN_CATALOG, + Visibility::VISIBILITY_BOTH, ], $this->_model->getVisibleInSiteVisibilities() ); $this->assertFalse($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_SEARCH); + $this->_model->setVisibility(Visibility::VISIBILITY_IN_SEARCH); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG); + $this->_model->setVisibility(Visibility::VISIBILITY_IN_CATALOG); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH); + $this->_model->setVisibility(Visibility::VISIBILITY_BOTH); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); } @@ -509,9 +562,9 @@ public function testValidate() )->setMetaDescription( 'meta description' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -551,9 +604,9 @@ public function testValidateUniqueInputAttributeValue() $attribute->getAttributeCode(), 'unique value' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -600,9 +653,9 @@ public function testValidateUniqueInputAttributeOnTheSameProduct() $attribute->getAttributeCode(), 'unique value' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -675,10 +728,10 @@ public function testSaveWithBackordersEnabled(int $qty, int $stockStatus, bool $ * @magentoDataFixture Magento/Catalog/_files/product_simple.php * * @return void - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\StateException + * @throws CouldNotSaveException + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException */ public function testProductStatusWhenCatalogFlatProductIsEnabled() { From 14926688ca962842341ca7e930e4537565fb478a Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Wed, 19 Aug 2020 09:57:02 +0300 Subject: [PATCH 29/95] rename action group --- ...p.xml => AdminOpenConfigGeneralAnalyticsPageActionGroup.xml} | 2 +- .../Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml | 2 +- .../Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml | 2 +- .../Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml | 2 +- .../Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename app/code/Magento/Analytics/Test/Mftf/ActionGroup/{AdminConfigGeneralAnalyticsPageOpenActionGroup.xml => AdminOpenConfigGeneralAnalyticsPageActionGroup.xml} (90%) diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.xml similarity index 90% rename from app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml rename to app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.xml index 713409e2b9a78..bfa3e436e09d0 100644 --- a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminConfigGeneralAnalyticsPageOpenActionGroup.xml +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.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="AdminConfigGeneralAnalyticsPageOpenActionGroup"> + <actionGroup name="AdminOpenConfigGeneralAnalyticsPageActionGroup"> <annotations> <description>Open Config General Analytics Page.</description> </annotations> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml index 3f960333f4dcb..a5b01a9221350 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml @@ -23,7 +23,7 @@ <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="--Please Select--" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml index 35f9e95885c0d..6116dd72528f7 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -23,7 +23,7 @@ <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingServiceLabel}}" userInput="Advanced Reporting Service" stepKey="seeAdvancedReportingServiceLabelEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml index a6cc8d7853cbe..1a77c365c8098 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -24,7 +24,7 @@ <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml index 020f2cf03974a..60585e73baeaa 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -24,7 +24,7 @@ <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminConfigGeneralAnalyticsPageOpenActionGroup" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingHour}}" userInput="23" stepKey="selectAdvancedReportingHour"/> From ff37e3f92b5c4d31e473bca3611e6f234e6dbbd6 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Wed, 19 Aug 2020 20:34:12 +0800 Subject: [PATCH 30/95] magento/adobe-stock-integration#1504:[Related PR] Insert rendition images to the content from media gallery instead of original images - Extract logic from controller to a model to enable before plugin interception of parameters --- .../Adminhtml/Wysiwyg/Images/OnInsert.php | 30 ++++------ .../Cms/Model/Wysiwyg/Images/PrepareImage.php | 60 +++++++++++++++++++ 2 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index 3244a7d14f0a3..6862efc924ab6 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -13,17 +13,25 @@ class OnInsert extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images */ protected $resultRawFactory; + /** + * @var \Magento\Cms\Model\Wysiwyg\Images\PrepareImage + */ + protected $prepareImage; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory + * @param \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $prepareImage */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\Controller\Result\RawFactory $resultRawFactory + \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, + \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $prepareImage ) { $this->resultRawFactory = $resultRawFactory; + $this->prepareImage = $prepareImage; parent::__construct($context, $coreRegistry); } @@ -34,26 +42,10 @@ public function __construct( */ public function execute() { - $imagesHelper = $this->_objectManager->get(\Magento\Cms\Helper\Wysiwyg\Images::class); $request = $this->getRequest(); - $storeId = $request->getParam('store'); - - $filename = $request->getParam('filename'); - $filename = $imagesHelper->idDecode($filename); - - $asIs = $request->getParam('as_is'); - - $forceStaticPath = $request->getParam('force_static_path'); - - $this->_objectManager->get(\Magento\Catalog\Helper\Data::class)->setStoreId($storeId); - $imagesHelper->setStoreId($storeId); - - if ($forceStaticPath) { - $image = parse_url($imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); - } else { - $image = $imagesHelper->getImageHtmlDeclaration($filename, $asIs); - } + /** @var \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $image */ + $image = $this->prepareImage->execute($request->getParams()); /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ $resultRaw = $this->resultRawFactory->create(); diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php new file mode 100644 index 0000000000000..852db8e9a0a78 --- /dev/null +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\Wysiwyg\Images; + +use Magento\Catalog\Helper\Data; +use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; + +class PrepareImage +{ + /** + * @var ImagesHelper + */ + private $imagesHelper; + + /** + * @var Data + */ + private $catalogHelper; + + /** + * PrepareImage constructor. + * @param ImagesHelper $imagesHelper + * @param Data $catalogHelper + */ + public function __construct( + ImagesHelper $imagesHelper, + Data $catalogHelper + ) { + $this->imagesHelper = $imagesHelper; + $this->catalogHelper = $catalogHelper; + } + + /** + * @param array $data + * @return string + */ + public function execute(array $data): string + { + $filename = $this->imagesHelper->idDecode($data['filename']); + $storeId = (int)$data['store_id']; + + $this->catalogHelper->setStoreId($storeId); + $this->imagesHelper->setStoreId($storeId); + + if ($data['force_static_path']) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $image = parse_url($this->imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); + } else { + $image = $this->imagesHelper->getImageHtmlDeclaration($filename, $data['as_is']); + } + + return $image; + } +} From 02934d4d87bbb8c7da3fa3cb0c8b8f8ac6f9faf9 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Mon, 17 Aug 2020 17:39:57 -0500 Subject: [PATCH 31/95] MC-36258: customer/section/load are failing with a 400 error - Fix error The "checkout-fields" section source isn't supported --- .../view/frontend/web/js/customer-data.js | 3 + .../frontend/js/customer-data.test.js | 213 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index 5c9bf431bac1d..5321dfecba182 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -261,6 +261,9 @@ define([ } }); + //remove expired section names of previously installed/enable modules + expiredSectionNames = _.intersection(expiredSectionNames, sectionConfig.getSectionNames()); + return _.uniq(expiredSectionNames); }, diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js new file mode 100644 index 0000000000000..7063b846ed166 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js @@ -0,0 +1,213 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*eslint-disable max-nested-callbacks*/ +/*jscs:disable jsDoc*/ + +define([ + 'jquery', + 'underscore', + 'Magento_Customer/js/section-config', + 'Magento_Customer/js/customer-data' +], function ( + $, + _, + sectionConfig, + customerData +) { + 'use strict'; + + var sectionConfigSettings = { + baseUrls: [ + 'http://localhost/' + ], + sections: { + 'customer/account/loginpost': ['*'], + 'checkout/cart/add': ['cart'], + 'rest/*/v1/guest-carts/*/selected-payment-method': ['cart','checkout-data'], + '*': ['messages'] + }, + clientSideSections: [ + 'checkout-data', + 'cart-data' + ], + sectionNames: [ + 'customer', + 'product_data_storage', + 'cart', + 'messages' + ] + }, + cookieLifeTime = 3600, + jQueryGetJSON; + + function init(config) { + var defaultConfig = { + sectionLoadUrl: 'http://localhost/customer/section/load/', + expirableSectionLifetime: 60, // minutes + expirableSectionNames: ['cart'], + cookieLifeTime: cookieLifeTime, + updateSessionUrl: 'http://localhost/customer/account/updateSession/' + }; + + customerData['Magento_Customer/js/customer-data']($.extend({}, defaultConfig, config || {})); + } + + function setupLocalStorage(sections) { + var mageCacheStorage = {}, + sectionDataIds = {}; + + _.each(sections, function (sectionData, sectionName) { + sectionDataIds[sectionName] = sectionData['data_id']; + + if (typeof sectionData.content !== 'undefined') { + mageCacheStorage[sectionName] = sectionData; + } + }); + + $.localStorage.set( + 'mage-cache-storage', + mageCacheStorage + ); + $.cookieStorage.set( + 'section_data_ids', + sectionDataIds + ); + + $.localStorage.set( + 'mage-cache-timeout', + new Date(Date.now() + cookieLifeTime * 1000) + ); + $.cookieStorage.set( + 'mage-cache-sessid', + true + ); + } + + function clearLocalStorage() { + $.cookieStorage.set('section_data_ids', {}); + + if (window.localStorage) { + window.localStorage.clear(); + } + } + + describe('Magento_Customer/js/customer-data', function () { + beforeAll(function () { + clearLocalStorage(); + }); + + beforeEach(function () { + jQueryGetJSON = $.getJSON; + sectionConfig['Magento_Customer/js/section-config'](sectionConfigSettings); + }); + + afterEach(function () { + $.getJSON = jQueryGetJSON; + clearLocalStorage(); + }); + + describe('getExpiredSectionNames()', function () { + it('check that result contains expired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago + 'content': {} + } + }); + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('check that result doest not contain unexpired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute + 'content': {} + } + }); + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); + + it('check that result contains invalidated section names', function () { + setupLocalStorage({ + 'cart': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('check that result does not contain unsupported section names', function () { + setupLocalStorage({ + 'catalog': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); + }); + + describe('init()', function () { + it('check that sections are not requested from server, if there are no expired sections', function () { + setupLocalStorage({ + 'catalog': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect($.getJSON).not.toHaveBeenCalled(); + }); + it('check that sections are requested from server, if there are expired sections', function () { + setupLocalStorage({ + 'customer': { + 'data_id': Math.floor(Date.now() / 1000) + 60 // invalidated, + }, + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago + 'content': {} + }, + 'product_data_storage': { + 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute + 'content': {} + }, + 'catalog': { + 'data_id': Math.floor(Date.now() / 1000) + 60 // invalid section, + }, + 'checkout': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // invalid section, + 'content': {} + } + }); + + $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect($.getJSON).toHaveBeenCalledWith( + 'http://localhost/customer/section/load/', + jasmine.objectContaining({ + sections: 'cart,customer' + }) + ); + }); + }); + }); +}); From 9c3588faf2b27faaf331f1e1599f3effb7491012 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Wed, 19 Aug 2020 15:11:34 -0500 Subject: [PATCH 32/95] MQE-2271: Release 3.1.0 Delivery Composer + staticRuleset update --- composer.json | 2 +- composer.lock | 636 +++++++++++++++++++++--- dev/tests/acceptance/staticRuleset.json | 3 +- 3 files changed, 571 insertions(+), 70 deletions(-) diff --git a/composer.json b/composer.json index 25be12b5bb72f..1f91e7b8594a1 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "friendsofphp/php-cs-fixer": "~2.16.0", "lusitanian/oauth": "~0.8.10", "magento/magento-coding-standard": "*", - "magento/magento2-functional-testing-framework": "^3.0", + "magento/magento2-functional-testing-framework": "^3.1", "pdepend/pdepend": "~2.7.1", "phpcompatibility/php-compatibility": "^9.3", "phpmd/phpmd": "^2.8.0", diff --git a/composer.lock b/composer.lock index c2eed9d87cc00..5b7e6c3da431a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0b51badfd1978bb34febd90226af9e27", + "content-hash": "b5562151b3be7e921e3ebc8080da557f", "packages": [ { "name": "colinmollenhour/cache-backend-file", @@ -206,6 +206,16 @@ "ssl", "tls" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], "time": "2020-04-08T08:27:21+00:00" }, { @@ -1346,12 +1356,6 @@ "BSD-3-Clause" ], "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T13:45:39+00:00" }, { @@ -3319,12 +3323,6 @@ "laminas", "zf" ], - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T16:45:56+00:00" }, { @@ -3564,16 +3562,6 @@ "logging", "psr-3" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], "time": "2020-05-22T07:31:27+00:00" }, { @@ -4366,16 +4354,6 @@ "parser", "validator" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], "time": "2020-04-30T19:05:18+00:00" }, { @@ -7337,6 +7315,555 @@ ], "time": "2020-06-27T23:57:46+00:00" }, + { + "name": "hoa/consistency", + "version": "1.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Consistency.git", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f", + "shasum": "" + }, + "require": { + "hoa/exception": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "hoa/stream": "~1.0", + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Consistency\\": "." + }, + "files": [ + "Prelude.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Consistency library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autoloader", + "callable", + "consistency", + "entity", + "flex", + "keyword", + "library" + ], + "time": "2017-05-02T12:18:12+00:00" + }, + { + "name": "hoa/console", + "version": "3.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Console.git", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Console/zipball/e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/file": "~1.0", + "hoa/protocol": "~1.0", + "hoa/stream": "~1.0", + "hoa/ustring": "~4.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-pcntl": "To enable hoa://Event/Console/Window:resize.", + "hoa/dispatcher": "To use the console kit.", + "hoa/router": "To use the console kit." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Console\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Console library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autocompletion", + "chrome", + "cli", + "console", + "cursor", + "getoption", + "library", + "option", + "parser", + "processus", + "readline", + "terminfo", + "tput", + "window" + ], + "time": "2017-05-02T12:26:19+00:00" + }, + { + "name": "hoa/event", + "version": "1.17.01.13", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Event.git", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Event\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Event library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "event", + "library", + "listener", + "observer" + ], + "time": "2017-01-13T15:30:50+00:00" + }, + { + "name": "hoa/exception", + "version": "1.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Exception.git", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Exception\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Exception library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "exception", + "library" + ], + "time": "2017-01-16T07:53:27+00:00" + }, + { + "name": "hoa/file", + "version": "1.17.07.11", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/File.git", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/iterator": "~2.0", + "hoa/stream": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\File\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\File library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Socket", + "directory", + "file", + "finder", + "library", + "link", + "temporary" + ], + "time": "2017-07-11T07:42:15+00:00" + }, + { + "name": "hoa/iterator", + "version": "2.17.01.10", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Iterator.git", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Iterator\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Iterator library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "iterator", + "library" + ], + "time": "2017-01-10T10:34:47+00:00" + }, + { + "name": "hoa/protocol", + "version": "1.17.01.14", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Protocol.git", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Protocol\\": "." + }, + "files": [ + "Wrapper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Protocol library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "protocol", + "resource", + "stream", + "wrapper" + ], + "time": "2017-01-14T12:26:10+00:00" + }, + { + "name": "hoa/stream", + "version": "1.17.02.21", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Stream.git", + "reference": "3293cfffca2de10525df51436adf88a559151d82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82", + "reference": "3293cfffca2de10525df51436adf88a559151d82", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/protocol": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Stream\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Stream library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Context", + "bucket", + "composite", + "filter", + "in", + "library", + "out", + "protocol", + "stream", + "wrapper" + ], + "time": "2017-02-21T16:01:06+00:00" + }, + { + "name": "hoa/ustring", + "version": "4.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Ustring.git", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/e6326e2739178799b1fe3fdd92029f9517fa17a0", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().", + "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Ustring\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Ustring library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "search", + "string", + "unicode" + ], + "time": "2017-01-16T07:08:25+00:00" + }, { "name": "jms/metadata", "version": "1.7.0", @@ -7709,16 +8236,16 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699" + "reference": "8a106ea029f222f4354854636861273c7577bee9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/8d98efa7434a30ab9e82ef128c430ef8e3a50699", - "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/8a106ea029f222f4354854636861273c7577bee9", + "reference": "8a106ea029f222f4354854636861273c7577bee9", "shasum": "" }, "require": { @@ -7736,6 +8263,7 @@ "ext-intl": "*", "ext-json": "*", "ext-openssl": "*", + "hoa/console": "~3.0", "monolog/monolog": "^1.17", "mustache/mustache": "~2.5", "php": "^7.3", @@ -7795,7 +8323,7 @@ "magento", "testing" ], - "time": "2020-07-09T21:26:19+00:00" + "time": "2020-08-19T19:57:27+00:00" }, { "name": "mikey179/vfsstream", @@ -8817,20 +9345,6 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], "time": "2020-05-05T12:55:44+00:00" }, { @@ -9120,12 +9634,6 @@ "keywords": [ "timer" ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-04-20T06:00:37+00:00" }, { @@ -9181,6 +9689,7 @@ "type": "github" } ], + "abandoned": true, "time": "2020-06-27T06:36:25+00:00" }, { @@ -9269,16 +9778,6 @@ "testing", "xunit" ], - "funding": [ - { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-05-22T13:54:05+00:00" }, { @@ -9786,6 +10285,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", + "abandoned": true, "time": "2020-02-08T06:07:58+00:00" }, { diff --git a/dev/tests/acceptance/staticRuleset.json b/dev/tests/acceptance/staticRuleset.json index 74fe3469e353b..82cc9dfe74152 100644 --- a/dev/tests/acceptance/staticRuleset.json +++ b/dev/tests/acceptance/staticRuleset.json @@ -2,6 +2,7 @@ "tests": [ "actionGroupArguments", "deprecatedEntityUsage", - "annotations" + "annotations", + "pauseActionUsage" ] } From 3dba58b321217cb186e03449c611382dc54a588f Mon Sep 17 00:00:00 2001 From: Victor Rad <vrad@magento.com> Date: Wed, 19 Aug 2020 15:29:43 -0500 Subject: [PATCH 33/95] MC-36598: Default Billing address is not selected after the same address checkbox unticked --- .../Test/Mftf/Section/CheckoutPaymentSection.xml | 2 ++ ...eckoutAsCustomerUsingNonDefaultAddressTest.xml | 5 +++-- .../Customer/Test/Mftf/Data/CustomerData.xml | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 5a9857f6aaa78..1c9933064154a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -15,6 +15,8 @@ <element name="billingNewAddressForm" type="text" selector="[data-form='billing-new-address']"/> <element name="billingAddressNotSameCheckbox" type="checkbox" selector="#billing-address-same-as-shipping-checkmo"/> <element name="editAddress" type="button" selector="button.action.action-edit-address"/> + <element name="addressDropdown" type="select" selector="[name=billing_address_id]"/> + <element name="addressDropdownSelected" type="select" selector="[name=billing_address_id] option:checked"/> <element name="placeOrderDisabled" type="button" selector="#checkout-payment-method-load button.disabled"/> <element name="update" type="button" selector=".payment-method._active .payment-method-billing-address .action.action-update"/> <element name="guestFirstName" type="input" selector=".payment-method._active .billing-address-form input[name='firstname']"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index 6a211c3908059..13968964436b4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -26,7 +26,7 @@ </createData> <!-- Create customer --> - <createData entity="Customer_US_UK_DE" stepKey="createCustomer"/> + <createData entity="Customer_DE_UK_US" stepKey="createCustomer"/> </before> <after> <!-- Admin log out --> @@ -70,7 +70,8 @@ <!-- Change the address --> <click selector="{{CheckoutPaymentSection.editAddress}}" stepKey="editAddress"/> - <waitForElementVisible selector="{{CheckoutShippingSection.addressDropdown}}" stepKey="waitForDropDownToBeVisible"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.addressDropdown}}" stepKey="waitForDropDownToBeVisible"/> + <see selector="{{CheckoutPaymentSection.addressDropdownSelected}}" userInput="{{US_Address_NY.street[0]}}" stepKey="seeDefaultBillingAddressStreet"/> <selectOption selector="{{CheckoutShippingSection.addressDropdown}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="addAddress"/> <!-- Check order summary in checkout --> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index e176c45a1fa00..5db0b8f5581d7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -285,6 +285,21 @@ <requiredEntity type="address">DE_Address_Berlin_Not_Default_Address</requiredEntity> <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> </entity> + <entity name="Customer_DE_UK_US" type="customer"> + <data key="group_id">1</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">DE_Address_Berlin_Not_Default_Address</requiredEntity> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + <requiredEntity type="address">US_Address_NY</requiredEntity> + </entity> <entity name="Retailer_Customer" type="customer"> <data key="group_id">3</data> <data key="default_billing">true</data> From f1271c0f4246cd014b9c3bee98371b64066f7d03 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Fri, 21 Aug 2020 03:24:51 +0800 Subject: [PATCH 34/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - initial modification --- ...sertCategoryGridPageDetailsActionGroup.xml | 20 +++++++++++-------- ...diaGalleryCatalogUiCategoryGridSection.xml | 16 +++++++-------- ...eryCatalogUiVerifyCategoryGridPageTest.xml | 4 +++- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml index 884fa47152932..92a56f3e37fbe 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml @@ -8,16 +8,20 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminAssertCategoryGridPageDetailsActionGroup"> + <arguments> + <argument name="category"/> + <argument name="imageName" type="string" defaultValue="magento"/> + </arguments> <annotations> - <description>Assert category grid page basic columns values for default category</description> + <description>Assert category grid page basic columns values for a specific 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"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('1', '0')}}" stepKey="assertProductsColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.inMenu('1', 'Yes')}}" stepKey="assertInMenuColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.enabled('1', 'Yes')}}" stepKey="assertEnabledColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(imageName)}}" stepKey="assertImageColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.path(category.name)}}" stepKey="assertPathColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name(category.name)}}" stepKey="assertNameColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.displayMode('PRODUCTS')}}" stepKey="assertDisplayModeColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('0')}}" stepKey="assertProductsColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.inMenu(category.include_in_menu)}}" stepKey="assertInMenuColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.enabled(category.is_active)}}" 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 ffd3c14c297c3..702be9ed4201d 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -10,13 +10,13 @@ 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="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"/> - <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"/> + <element name="image" type="text" selector="//tr//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//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Path')]/preceding-sibling::th) +1 ]//*[contains(text(), '{{categoryName}}')]" parameterized="true"/> + <element name="name" type="text" selector="//tr//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//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//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//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//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//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{edit}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml index 6b7bd3ba11f45..8fae844f9883a 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml @@ -26,6 +26,8 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> - <actionGroup ref="AdminAssertCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"/> + <actionGroup ref="AdminAssertCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> + <argument name="category" value="$$category$$"/> + </actionGroup> </test> </tests> From 29d07075a138de6fe2f9cc0b7d06862ef7e59a42 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Fri, 21 Aug 2020 14:23:11 +0800 Subject: [PATCH 35/95] magento/adobe-stock-integration#1504: Insert rendition images to the content from media gallery instead of original images - Extracted logic from wysiwyg OnInsert controller to a model --- .../Adminhtml/Wysiwyg/Images/OnInsert.php | 26 +++++++++------ ...areImage.php => GetInsertImageContent.php} | 32 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) rename app/code/Magento/Cms/Model/Wysiwyg/Images/{PrepareImage.php => GetInsertImageContent.php} (57%) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index 6862efc924ab6..c10547e0b2556 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -14,25 +14,26 @@ class OnInsert extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images protected $resultRawFactory; /** - * @var \Magento\Cms\Model\Wysiwyg\Images\PrepareImage + * @var \Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent */ - protected $prepareImage; + protected $getInsertImageContent; /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory - * @param \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $prepareImage + * @param \Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent $getInsertImageContent */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, - \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $prepareImage + ?\Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent $getInsertImageContent = null ) { $this->resultRawFactory = $resultRawFactory; - $this->prepareImage = $prepareImage; parent::__construct($context, $coreRegistry); + $this->getInsertImageContent = $getInsertImageContent ?: $this->_objectManager + ->get('Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent'); } /** @@ -42,13 +43,18 @@ public function __construct( */ public function execute() { - $request = $this->getRequest(); - - /** @var \Magento\Cms\Model\Wysiwyg\Images\PrepareImage $image */ - $image = $this->prepareImage->execute($request->getParams()); + $data = $this->getRequest()->getParams(); /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ $resultRaw = $this->resultRawFactory->create(); - return $resultRaw->setContents($image); + + return $resultRaw->setContents( + $this->getInsertImageContent->execute( + $data['filename'], + (int)$data['store_id'], + $data['force_static_path'], + $data['as_is'] + ) + ); } } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php similarity index 57% rename from app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php rename to app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php index 852db8e9a0a78..49ef32fffd7a7 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/PrepareImage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -11,7 +11,7 @@ use Magento\Catalog\Helper\Data; use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; -class PrepareImage +class GetInsertImageContent { /** * @var ImagesHelper @@ -25,6 +25,7 @@ class PrepareImage /** * PrepareImage constructor. + * * @param ImagesHelper $imagesHelper * @param Data $catalogHelper */ @@ -37,24 +38,33 @@ public function __construct( } /** - * @param array $data + * Prepare Image Contents for Insert + * + * @param string $encodedFilename + * @param int $storeId + * @param bool $forceStaticPath + * @param bool $renderAsTag * @return string */ - public function execute(array $data): string - { - $filename = $this->imagesHelper->idDecode($data['filename']); - $storeId = (int)$data['store_id']; + public function execute( + string $encodedFilename, + int $storeId, + bool $forceStaticPath, + bool $renderAsTag + ): string { + $filename = $this->imagesHelper->idDecode($encodedFilename); $this->catalogHelper->setStoreId($storeId); $this->imagesHelper->setStoreId($storeId); - if ($data['force_static_path']) { + if ($forceStaticPath) { // phpcs:ignore Magento2.Functions.DiscouragedFunction - $image = parse_url($this->imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); - } else { - $image = $this->imagesHelper->getImageHtmlDeclaration($filename, $data['as_is']); + return parse_url( + $this->imagesHelper->getCurrentUrl() . $filename, + PHP_URL_PATH + ); } - return $image; + return $this->imagesHelper->getImageHtmlDeclaration($filename, $renderAsTag); } } From e228d7670177a7023c991243cae74785f2faeeb9 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Fri, 21 Aug 2020 17:22:20 +0800 Subject: [PATCH 36/95] magento/adobe-stock-integration#1504:Extract logic from controller to a model to enable before plugin interception of parameters - Test failure fixes. Made store_id parameter optional. --- .../Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php | 4 ++-- .../Cms/Model/Wysiwyg/Images/GetInsertImageContent.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index c10547e0b2556..5ac16cf3a7c2c 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -51,9 +51,9 @@ public function execute() return $resultRaw->setContents( $this->getInsertImageContent->execute( $data['filename'], - (int)$data['store_id'], $data['force_static_path'], - $data['as_is'] + $data['as_is'], + isset($data['store_id']) ? (int) $data['store_id'] : null ) ); } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php index 49ef32fffd7a7..e9aab6160b259 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -41,16 +41,16 @@ public function __construct( * Prepare Image Contents for Insert * * @param string $encodedFilename - * @param int $storeId * @param bool $forceStaticPath * @param bool $renderAsTag + * @param int|null $storeId * @return string */ public function execute( string $encodedFilename, - int $storeId, bool $forceStaticPath, - bool $renderAsTag + bool $renderAsTag, + ?int $storeId = null ): string { $filename = $this->imagesHelper->idDecode($encodedFilename); From 408cdb75204f9d95ff3b8dfadf881318c703ec68 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Fri, 21 Aug 2020 22:01:21 +0800 Subject: [PATCH 37/95] magento/adobe-stock-integration#1504:Extract logic from controller to a model to enable before plugin interception of parameters - Fix static test --- .../Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index 5ac16cf3a7c2c..d504a211e7861 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -33,7 +33,7 @@ public function __construct( $this->resultRawFactory = $resultRawFactory; parent::__construct($context, $coreRegistry); $this->getInsertImageContent = $getInsertImageContent ?: $this->_objectManager - ->get('Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent'); + ->get(\Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent::class); } /** From 4fd39eac5c9aa26b55f7caff296000201a2acdb3 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 21 Aug 2020 10:20:46 -0500 Subject: [PATCH 38/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Model/Plugin/CustomerAuthorization.php | 80 +++++++++++++++++++ .../Magento/Persistent/etc/webapi_rest/di.xml | 3 + .../Magento/Persistent/etc/webapi_soap/di.xml | 3 + 3 files changed, 86 insertions(+) create mode 100644 app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php diff --git a/app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php new file mode 100644 index 0000000000000..d7475c725b481 --- /dev/null +++ b/app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Persistent\Model\Plugin; + +use Closure; +use Magento\Authorization\Model\UserContextInterface; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\Authorization; +use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; +use Magento\Persistent\Helper\Session as PersistentSession; + +/** + * Plugin around \Magento\Framework\Authorization::isAllowed + * + * Performs the check if the customer is logged in prior placing order on his behalf when the persistent cart is active + */ +class CustomerAuthorization +{ + /** + * @var UserContextInterface + */ + private $userContext; + + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * @var PersistentSession + */ + private $persistentSession; + + /** + * @param UserContextInterface $userContext + * @param CustomerSession $customerSession + * @param PersistentSession $persistentSession + */ + public function __construct( + UserContextInterface $userContext, + CustomerSession $customerSession, + PersistentSession $persistentSession + ) { + $this->userContext = $userContext; + $this->customerSession = $customerSession; + $this->persistentSession = $persistentSession; + } + + /** + * Check if the customer is logged in prior placing order on his behalf when the persistent cart is active + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param Authorization $subject + * @param Closure $proceed + * @param $resource + * @param null $privilege + * @return false|mixed + */ + public function aroundIsAllowed( + Authorization $subject, + Closure $proceed, + $resource, + $privilege = null + ) { + if ($resource == AuthorizationService::PERMISSION_SELF + && $this->userContext->getUserId() + && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER + && !$this->customerSession->isLoggedIn() + && $this->persistentSession->isPersistent() + ) { + return false; + } + + return true; + } +} diff --git a/app/code/Magento/Persistent/etc/webapi_rest/di.xml b/app/code/Magento/Persistent/etc/webapi_rest/di.xml index cb0aec6b460af..21a47576b1a08 100644 --- a/app/code/Magento/Persistent/etc/webapi_rest/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_rest/di.xml @@ -13,4 +13,7 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> + <type name="Magento\Framework\Authorization"> + <plugin name="customerAuthorization" type="Magento\Persistent\Model\Plugin\CustomerAuthorization" /> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_soap/di.xml b/app/code/Magento/Persistent/etc/webapi_soap/di.xml index cb0aec6b460af..21a47576b1a08 100644 --- a/app/code/Magento/Persistent/etc/webapi_soap/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_soap/di.xml @@ -13,4 +13,7 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> + <type name="Magento\Framework\Authorization"> + <plugin name="customerAuthorization" type="Magento\Persistent\Model\Plugin\CustomerAuthorization" /> + </type> </config> From 228ebc5443a1c0d761e6015651b8aa176a2cf351 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Sat, 22 Aug 2020 00:31:35 +0800 Subject: [PATCH 39/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - modifications on enabled and in menu columns and mftf files --- .../AdminMediaGalleryCatalogUiCategoryGridSection.xml | 6 +++--- .../Ui/Component/Listing/Columns/InMenu.php | 4 ++-- .../Ui/Component/Listing/Columns/IsActive.php | 4 ++-- .../ui_component/media_gallery_category_listing.xml | 2 ++ ...iaGalleryAssertCategoryNameInCategoryGridActionGroup.xml | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 702be9ed4201d..a5ed4f54eca0f 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -15,8 +15,8 @@ <element name="name" type="text" selector="//tr//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//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//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//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//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//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{edit}}']" parameterized="true"/> + <element name="inMenu" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'In Menu')]/preceding-sibling::th) +1 ]//span[contains(@class, '{{inMenuValue}}')]" parameterized="true"/> + <element name="enabled" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Enabled')]/preceding-sibling::th) +1 ]//span[contains(@class, '{{enabledValue}}')]" parameterized="true"/> + <element name="edit" type="button" selector="//tr[td//text()[contains(., '{{categoryName}}')]]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{actionButton}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php index fe4720b4a3e60..d1c2331b3f141 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = 'Yes'; + $item[$fieldName] = '<span class="true">Yes</span>'; } else { - $item[$fieldName] = 'No'; + $item[$fieldName] = '<span class="false">No</span>'; } } } diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php index c6f20c937d5b3..3485f54bdc68c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = 'Yes'; + $item[$fieldName] = '<span class="true">Yes</span>'; } else { - $item[$fieldName] = 'No'; + $item[$fieldName] = '<span class="false">No</span>'; } } } 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 e12d90b95303b..94ef605f856c7 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 @@ -170,11 +170,13 @@ <column name="include_in_menu" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\InMenu"> <settings> <label translate="true">In Menu</label> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> </settings> </column> <column name="is_active" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\IsActive"> <settings> <label translate="true">Enabled</label> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> </settings> </column> <actionsColumn name="actions" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\CategoryActions" sortOrder="1000"> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml index 42d723f0811d3..cc4632bbdb86e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml @@ -16,6 +16,6 @@ <argument name="categoryName" type="string"/> </arguments> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name('1', categoryName)}}" stepKey="assertNameColumn"/> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name(categoryName)}}" stepKey="assertNameColumn"/> </actionGroup> </actionGroups> From b44b04681b86e0de5b84ac5265aeb1c223d4e05a Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Sat, 22 Aug 2020 01:25:26 +0800 Subject: [PATCH 40/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - additional modifications for in menu and enabled column renderer and mftf files --- .../AdminAssertCategoryGridPageDetailsActionGroup.xml | 1 - .../Ui/Component/Listing/Columns/InMenu.php | 4 ++-- .../Ui/Component/Listing/Columns/IsActive.php | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml index 92a56f3e37fbe..958b733613c50 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml @@ -19,7 +19,6 @@ <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(imageName)}}" stepKey="assertImageColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.path(category.name)}}" stepKey="assertPathColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name(category.name)}}" stepKey="assertNameColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.displayMode('PRODUCTS')}}" stepKey="assertDisplayModeColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('0')}}" stepKey="assertProductsColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.inMenu(category.include_in_menu)}}" stepKey="assertInMenuColumn"/> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.enabled(category.is_active)}}" stepKey="assertEnabledColumn"/> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php index d1c2331b3f141..00b5dd87e8cf1 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = '<span class="true">Yes</span>'; + $item[$fieldName] = '<span class="1">Yes</span>'; } else { - $item[$fieldName] = '<span class="false">No</span>'; + $item[$fieldName] = '<span class="0">No</span>'; } } } diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php index 3485f54bdc68c..48464d6ffdcbc 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = '<span class="true">Yes</span>'; + $item[$fieldName] = '<span class="1">Yes</span>'; } else { - $item[$fieldName] = '<span class="false">No</span>'; + $item[$fieldName] = '<span class="0">No</span>'; } } } From 3e0320f303de2c06e38d330f9197b6a25af9fd60 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 21 Aug 2020 17:40:20 -0500 Subject: [PATCH 41/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Customer/Model/Customer/Authorization.php | 77 +++++++++++++++++++ .../Model/Customer/AuthorizationComposite.php | 41 ++++++++++ .../Model/Plugin/CustomerAuthorization.php | 74 ++++-------------- .../Magento/Customer/etc/webapi_rest/di.xml | 9 +++ .../Magento/Customer/etc/webapi_soap/di.xml | 9 +++ .../Authorization.php} | 32 ++------ .../Magento/Persistent/etc/webapi_rest/di.xml | 8 +- .../Magento/Persistent/etc/webapi_soap/di.xml | 8 +- 8 files changed, 172 insertions(+), 86 deletions(-) create mode 100644 app/code/Magento/Customer/Model/Customer/Authorization.php create mode 100644 app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php rename app/code/Magento/Persistent/Model/{Plugin/CustomerAuthorization.php => Customer/Authorization.php} (62%) diff --git a/app/code/Magento/Customer/Model/Customer/Authorization.php b/app/code/Magento/Customer/Model/Customer/Authorization.php new file mode 100644 index 0000000000000..f4e6d07affc84 --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/Authorization.php @@ -0,0 +1,77 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Model\Customer; + +use Magento\Authorization\Model\UserContextInterface; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\ResourceModel\Customer as CustomerResource; +use Magento\Framework\AuthorizationInterface; +use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class to invalidate user credentials + */ +class Authorization implements AuthorizationInterface +{ + /** + * @var UserContextInterface + */ + private $userContext; + + /** + * @var CustomerFactory + */ + private $customerFactory; + + /** + * @var CustomerResource + */ + private $customerResource; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param UserContextInterface $userContext + * @param CustomerFactory $customerFactory + * @param CustomerResource $customerResource + * @param StoreManagerInterface $storeManager + */ + public function __construct( + UserContextInterface $userContext, + CustomerFactory $customerFactory, + CustomerResource $customerResource, + StoreManagerInterface $storeManager + ) { + $this->userContext = $userContext; + $this->customerFactory = $customerFactory; + $this->customerResource = $customerResource; + $this->storeManager = $storeManager; + } + + public function isAllowed($resource, $privilege = null) + { + if ($resource == AuthorizationService::PERMISSION_SELF + && $this->userContext->getUserId() + && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER + ) { + $customer = $this->customerFactory->create(); + $this->customerResource->load($customer, $this->userContext->getUserId()); + $currentStoreId = $this->storeManager->getStore()->getId(); + $sharedStoreIds = $customer->getSharedStoreIds(); + if (in_array($currentStoreId, $sharedStoreIds)) { + return true; + } + } + + return false; + } +} diff --git a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php new file mode 100644 index 0000000000000..e643460a32789 --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php @@ -0,0 +1,41 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Model\Customer; + +use Magento\Framework\AuthorizationInterface; + +/** + * Class to invalidate user credentials + */ +class AuthorizationComposite implements AuthorizationInterface +{ + /** + * @var AuthorizationInterface[] + */ + private $authorizationChecks; + + public function __construct( + array $authorizationChecks + ) { + $this->authorizationChecks = $authorizationChecks; + } + + public function isAllowed($resource, $privilege = null) + { + $result = false; + + foreach ($this->authorizationChecks as $authorizationCheck) { + $result = $authorizationCheck->isAllowed($resource, $privilege); + if (!$result) { + break; + } + } + + return $result; + } +} diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index b877b2cca67a5..292c22fba512b 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -6,11 +6,9 @@ namespace Magento\Customer\Model\Plugin; -use Magento\Authorization\Model\UserContextInterface; -use Magento\Customer\Model\CustomerFactory; -use Magento\Customer\Model\ResourceModel\Customer as CustomerResource; -use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; -use Magento\Store\Model\StoreManagerInterface; +use Closure; +use Magento\Customer\Model\Customer\AuthorizationComposite; +use Magento\Framework\Authorization; /** * Plugin around \Magento\Framework\Authorization::isAllowed @@ -20,74 +18,36 @@ class CustomerAuthorization { /** - * @var UserContextInterface + * @var AuthorizationComposite */ - private $userContext; - - /** - * @var CustomerFactory - */ - private $customerFactory; - - /** - * @var CustomerResource - */ - private $customerResource; - - /** - * @var StoreManagerInterface - */ - private $storeManager; + private $authorizationComposite; /** * Inject dependencies. - * - * @param UserContextInterface $userContext - * @param CustomerFactory $customerFactory - * @param CustomerResource $customerResource - * @param StoreManagerInterface $storeManager + * @param AuthorizationComposite $composite */ public function __construct( - UserContextInterface $userContext, - CustomerFactory $customerFactory, - CustomerResource $customerResource, - StoreManagerInterface $storeManager + AuthorizationComposite $composite ) { - $this->userContext = $userContext; - $this->customerFactory = $customerFactory; - $this->customerResource = $customerResource; - $this->storeManager = $storeManager; + $this->authorizationComposite = $composite; } /** - * Check if resource for which access is needed has self permissions defined in webapi config. - * - * @param \Magento\Framework\Authorization $subject - * @param callable $proceed - * @param string $resource - * @param string $privilege - * - * @return bool true If resource permission is self, to allow - * customer access without further checks in parent method * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param Authorization $subject + * @param Closure $proceed + * @param $resource + * @param null $privilege + * @return bool|mixed */ public function aroundIsAllowed( - \Magento\Framework\Authorization $subject, - \Closure $proceed, + Authorization $subject, + Closure $proceed, $resource, $privilege = null ) { - if ($resource == AuthorizationService::PERMISSION_SELF - && $this->userContext->getUserId() - && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER - ) { - $customer = $this->customerFactory->create(); - $this->customerResource->load($customer, $this->userContext->getUserId()); - $currentStoreId = $this->storeManager->getStore()->getId(); - $sharedStoreIds = $customer->getSharedStoreIds(); - if (in_array($currentStoreId, $sharedStoreIds)) { - return true; - } + if ($this->authorizationComposite->isAllowed($resource, $privilege)) { + return true; } return $proceed($resource, $privilege); diff --git a/app/code/Magento/Customer/etc/webapi_rest/di.xml b/app/code/Magento/Customer/etc/webapi_rest/di.xml index a349d07a5e222..d07d1a61c3d62 100644 --- a/app/code/Magento/Customer/etc/webapi_rest/di.xml +++ b/app/code/Magento/Customer/etc/webapi_rest/di.xml @@ -22,4 +22,13 @@ <type name="Magento\Customer\Api\CustomerRepositoryInterface"> <plugin name="updateCustomerByIdFromRequest" type="Magento\Customer\Model\Plugin\UpdateCustomer" /> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="rest_customer_authorization" xsi:type="object"> + Magento\Customer\Model\Customer\Authorization + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/etc/webapi_soap/di.xml b/app/code/Magento/Customer/etc/webapi_soap/di.xml index 646ba98b4c5d8..c23de8ef3f7e1 100644 --- a/app/code/Magento/Customer/etc/webapi_soap/di.xml +++ b/app/code/Magento/Customer/etc/webapi_soap/di.xml @@ -9,4 +9,13 @@ <type name="Magento\Framework\Authorization"> <plugin name="customerAuthorization" type="Magento\Customer\Model\Plugin\CustomerAuthorization" /> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="soap_customer_authorization" xsi:type="object"> + Magento\Customer\Model\Customer\Authorization + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php similarity index 62% rename from app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php rename to app/code/Magento/Persistent/Model/Customer/Authorization.php index d7475c725b481..e9687b2193475 100644 --- a/app/code/Magento/Persistent/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -4,21 +4,15 @@ * See COPYING.txt for license details. */ -namespace Magento\Persistent\Model\Plugin; +namespace Magento\Persistent\Model\Customer; -use Closure; use Magento\Authorization\Model\UserContextInterface; use Magento\Customer\Model\Session as CustomerSession; -use Magento\Framework\Authorization; +use Magento\Framework\AuthorizationInterface; use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; use Magento\Persistent\Helper\Session as PersistentSession; -/** - * Plugin around \Magento\Framework\Authorization::isAllowed - * - * Performs the check if the customer is logged in prior placing order on his behalf when the persistent cart is active - */ -class CustomerAuthorization +class Authorization implements AuthorizationInterface { /** * @var UserContextInterface @@ -50,31 +44,19 @@ public function __construct( $this->persistentSession = $persistentSession; } - /** - * Check if the customer is logged in prior placing order on his behalf when the persistent cart is active - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @param Authorization $subject - * @param Closure $proceed - * @param $resource - * @param null $privilege - * @return false|mixed - */ - public function aroundIsAllowed( - Authorization $subject, - Closure $proceed, + public function isAllowed( $resource, $privilege = null ) { if ($resource == AuthorizationService::PERMISSION_SELF && $this->userContext->getUserId() && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER - && !$this->customerSession->isLoggedIn() + && $this->customerSession->isLoggedIn() && $this->persistentSession->isPersistent() ) { - return false; + return true; } - return true; + return false; } } diff --git a/app/code/Magento/Persistent/etc/webapi_rest/di.xml b/app/code/Magento/Persistent/etc/webapi_rest/di.xml index 21a47576b1a08..89504f0471788 100644 --- a/app/code/Magento/Persistent/etc/webapi_rest/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_rest/di.xml @@ -13,7 +13,11 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> - <type name="Magento\Framework\Authorization"> - <plugin name="customerAuthorization" type="Magento\Persistent\Model\Plugin\CustomerAuthorization" /> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="persistent_rest_customer_authorization" xsi:type="object">Magento\Persistent\Model\Customer\Authorization</item> + </argument> + </arguments> </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_soap/di.xml b/app/code/Magento/Persistent/etc/webapi_soap/di.xml index 21a47576b1a08..2a440fff03598 100644 --- a/app/code/Magento/Persistent/etc/webapi_soap/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_soap/di.xml @@ -13,7 +13,11 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> - <type name="Magento\Framework\Authorization"> - <plugin name="customerAuthorization" type="Magento\Persistent\Model\Plugin\CustomerAuthorization" /> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="persistent_soap_customer_authorization" xsi:type="object">Magento\Persistent\Model\Customer\Authorization</item> + </argument> + </arguments> </type> </config> From 45849cc9d01bbc223fbda72d9748a22561084e10 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Mon, 24 Aug 2020 13:02:34 +0800 Subject: [PATCH 42/95] magento/adobe-stock-integration#1504: Extracted logic from wysiwyg OnInsert controller to a model - Integration test --- .../Images/GetInsertImageContentTest.php | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php diff --git a/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php b/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php new file mode 100644 index 0000000000000..7ae581545c85e --- /dev/null +++ b/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Test\Integration\Model\Wysiwyg\Images; + +use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; +use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class GetInsertImageContentTest extends TestCase +{ + private const TEST_IMAGE_FILE = '/test-image.jpg'; + + /** + * @var GetInsertImageContent + */ + private $getInsertImageContent; + + /** + * @var ImagesHelper + */ + private $imagesHelper; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->getInsertImageContent = Bootstrap::getObjectManager()->get(GetInsertImageContent::class); + $this->imagesHelper = Bootstrap::getObjectManager()->get(ImagesHelper::class); + } + + /** + * Test for GetInsertImageContent::execute + * + * @dataProvider imageDataProvider + * @param string $encodedFilename + * @param bool $forceStaticPath + * @param bool $renderAsTag + * @param int|null $storeId + */ + public function testExecute( + string $encodedFilename, + bool $forceStaticPath, + bool $renderAsTag, + ?int $storeId = null + ): void { + $getImageForInsert = $this->getInsertImageContent->execute( + $encodedFilename, + $forceStaticPath, + $renderAsTag, + $storeId + ); + + if (!$forceStaticPath) { + if ($renderAsTag) { + $html = $this->imagesHelper->getImageHtmlDeclaration( + self::TEST_IMAGE_FILE, + true + ); + $this->assertEquals( + $getImageForInsert, + $html + ); + } else { + $html = $this->imagesHelper->getImageHtmlDeclaration( + self::TEST_IMAGE_FILE, + false + ); + $this->assertEquals( + $getImageForInsert, + $html + ); + } + } else { + $this->assertEquals( + $getImageForInsert, + parse_url( + $this->imagesHelper->getCurrentUrl() . self::TEST_IMAGE_FILE, + PHP_URL_PATH + ) + ); + } + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function imageDataProvider(): array + { + return [ + [ + 'L3Rlc3QtaW1hZ2UuanBn', + false, + true, + 1, + ], + [ + 'L3Rlc3QtaW1hZ2UuanBn', + true, + false, + 1, + ], + [ + 'L3Rlc3QtaW1hZ2UuanBn', + false, + false, + 1, + ], + [ + 'L3Rlc3QtaW1hZ2UuanBn', + false, + true, + 2, + ], + [ + 'L3Rlc3QtaW1hZ2UuanBn', + false, + true, + null, + ], + ]; + } +} From 40cb692368e9ada9817d682f00358736394df752 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Mon, 24 Aug 2020 18:16:48 +0800 Subject: [PATCH 43/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - renamed action group based on requested change --- ...up.xml => AssertAdminCategoryGridPageDetailsActionGroup.xml} | 2 +- .../AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/{AdminAssertCategoryGridPageDetailsActionGroup.xml => AssertAdminCategoryGridPageDetailsActionGroup.xml} (95%) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml similarity index 95% rename from app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml rename to app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml index 958b733613c50..f86e04ad7c4f2 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminAssertCategoryGridPageDetailsActionGroup"> + <actionGroup name="AssertAdminCategoryGridPageDetailsActionGroup"> <arguments> <argument name="category"/> <argument name="imageName" type="string" defaultValue="magento"/> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml index 8fae844f9883a..b2668b59fb8a1 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml @@ -26,7 +26,7 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> - <actionGroup ref="AdminAssertCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> <argument name="category" value="$$category$$"/> </actionGroup> </test> From 5a7206b4ebde48d4641517e4b1812d5a15a4d5e7 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Mon, 24 Aug 2020 20:26:13 +0800 Subject: [PATCH 44/95] magento/adobe-stock-integration#1776: [MFTF] Sorting in media gallery - initial implementation --- .../Test/AdminMediaGallerySortingTest.xml | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml new file mode 100644 index 0000000000000..a66d3d2af9182 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortingTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sorting in Standalone Media Gallery"/> + <stories value="User uses Sorting in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sorting in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createNewFolder"> + <argument name="name" value="a"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertNewFolderCreated"/> + </test> +</tests> From fe8771e08ba3f1a1e956d9531fb4448963c5603c Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Mon, 24 Aug 2020 07:57:04 -0500 Subject: [PATCH 45/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Persistent/Model/Customer/Authorization.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Persistent/Model/Customer/Authorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php index e9687b2193475..7852000ad525a 100644 --- a/app/code/Magento/Persistent/Model/Customer/Authorization.php +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -48,15 +48,15 @@ public function isAllowed( $resource, $privilege = null ) { - if ($resource == AuthorizationService::PERMISSION_SELF + if ($this->persistentSession->isPersistent() + && $resource == AuthorizationService::PERMISSION_SELF && $this->userContext->getUserId() && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER - && $this->customerSession->isLoggedIn() - && $this->persistentSession->isPersistent() + && !$this->customerSession->isLoggedIn() ) { - return true; + return false; } - return false; + return true; } } From f794b771c3e1e028519a52222cf299f723fc22ff Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Mon, 24 Aug 2020 08:11:06 -0500 Subject: [PATCH 46/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Persistent/Model/Customer/Authorization.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/app/code/Magento/Persistent/Model/Customer/Authorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php index 7852000ad525a..156a55b39991b 100644 --- a/app/code/Magento/Persistent/Model/Customer/Authorization.php +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -6,19 +6,12 @@ namespace Magento\Persistent\Model\Customer; -use Magento\Authorization\Model\UserContextInterface; use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\AuthorizationInterface; -use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; use Magento\Persistent\Helper\Session as PersistentSession; class Authorization implements AuthorizationInterface { - /** - * @var UserContextInterface - */ - private $userContext; - /** * @var CustomerSession */ @@ -30,16 +23,13 @@ class Authorization implements AuthorizationInterface private $persistentSession; /** - * @param UserContextInterface $userContext * @param CustomerSession $customerSession * @param PersistentSession $persistentSession */ public function __construct( - UserContextInterface $userContext, CustomerSession $customerSession, PersistentSession $persistentSession ) { - $this->userContext = $userContext; $this->customerSession = $customerSession; $this->persistentSession = $persistentSession; } @@ -48,12 +38,7 @@ public function isAllowed( $resource, $privilege = null ) { - if ($this->persistentSession->isPersistent() - && $resource == AuthorizationService::PERMISSION_SELF - && $this->userContext->getUserId() - && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER - && !$this->customerSession->isLoggedIn() - ) { + if ($this->persistentSession->isPersistent() && !$this->customerSession->isLoggedIn()) { return false; } From 10bd41f8b0f592fcdfa02cece19347d2c31ddd32 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Mon, 24 Aug 2020 23:53:12 +0800 Subject: [PATCH 47/95] magento/adobe-stock-integration#1504: Extracted logic from wysiwyg OnInsert controller to a model - PR suggestions and Adjustments on Integration test --- .../Adminhtml/Wysiwyg/Images/OnInsert.php | 13 ++-- .../Wysiwyg/Images/GetInsertImageContent.php | 15 +---- .../Images/GetInsertImageContentTest.php | 61 +++++++++++-------- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index d504a211e7861..f62d3debc74bd 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -45,15 +45,20 @@ public function execute() { $data = $this->getRequest()->getParams(); + $fileName = $data['filename']; + $forceStaticPath = $data['force_static_path']; + $renderAsTag = $data['as_is']; + $storeId = isset($data['store_id']) ? (int) $data['store_id'] : null; + /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ $resultRaw = $this->resultRawFactory->create(); return $resultRaw->setContents( $this->getInsertImageContent->execute( - $data['filename'], - $data['force_static_path'], - $data['as_is'], - isset($data['store_id']) ? (int) $data['store_id'] : null + $fileName, + $forceStaticPath, + $renderAsTag, + $storeId ) ); } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php index e9aab6160b259..e6f59c484bfc2 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -8,7 +8,6 @@ namespace Magento\Cms\Model\Wysiwyg\Images; -use Magento\Catalog\Helper\Data; use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; class GetInsertImageContent @@ -18,23 +17,14 @@ class GetInsertImageContent */ private $imagesHelper; - /** - * @var Data - */ - private $catalogHelper; - /** * PrepareImage constructor. * * @param ImagesHelper $imagesHelper - * @param Data $catalogHelper */ - public function __construct( - ImagesHelper $imagesHelper, - Data $catalogHelper - ) { + public function __construct(ImagesHelper $imagesHelper) + { $this->imagesHelper = $imagesHelper; - $this->catalogHelper = $catalogHelper; } /** @@ -54,7 +44,6 @@ public function execute( ): string { $filename = $this->imagesHelper->idDecode($encodedFilename); - $this->catalogHelper->setStoreId($storeId); $this->imagesHelper->setStoreId($storeId); if ($forceStaticPath) { diff --git a/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php b/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php index 7ae581545c85e..fb026f6b4292a 100644 --- a/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php +++ b/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php @@ -8,8 +8,10 @@ namespace Magento\Cms\Test\Integration\Model\Wysiwyg\Images; +use Magento\Backend\Helper\Data as BackendHelper; use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; +use Magento\Framework\Url\EncoderInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -27,6 +29,16 @@ class GetInsertImageContentTest extends TestCase */ private $imagesHelper; + /** + * @var EncoderInterface + */ + private $urlEncoder; + + /** + * @var BackendHelper + */ + protected $_backendData; + /** * @inheritdoc */ @@ -34,6 +46,8 @@ protected function setUp(): void { $this->getInsertImageContent = Bootstrap::getObjectManager()->get(GetInsertImageContent::class); $this->imagesHelper = Bootstrap::getObjectManager()->get(ImagesHelper::class); + $this->urlEncoder = Bootstrap::getObjectManager()->get(EncoderInterface::class); + $this->_backendData = Bootstrap::getObjectManager()->get(BackendHelper::class); } /** @@ -44,12 +58,14 @@ protected function setUp(): void * @param bool $forceStaticPath * @param bool $renderAsTag * @param int|null $storeId + * @param string|null $expectedResult */ public function testExecute( string $encodedFilename, bool $forceStaticPath, bool $renderAsTag, - ?int $storeId = null + ?int $storeId = null, + ?string $expectedResult = null ): void { $getImageForInsert = $this->getInsertImageContent->execute( $encodedFilename, @@ -58,35 +74,21 @@ public function testExecute( $storeId ); - if (!$forceStaticPath) { - if ($renderAsTag) { - $html = $this->imagesHelper->getImageHtmlDeclaration( - self::TEST_IMAGE_FILE, - true - ); - $this->assertEquals( - $getImageForInsert, - $html - ); - } else { - $html = $this->imagesHelper->getImageHtmlDeclaration( - self::TEST_IMAGE_FILE, - false - ); - $this->assertEquals( - $getImageForInsert, - $html + if (!$forceStaticPath && !$renderAsTag) { + if (!$this->imagesHelper->isUsingStaticUrlsAllowed()) { + + $encodedDirective = $this->urlEncoder->encode($expectedResult); + $expectedResult = $this->_backendData->getUrl( + 'cms/wysiwyg/directive', + [ + '___directive' => $encodedDirective, + '_escape_params' => false, + ] ); } - } else { - $this->assertEquals( - $getImageForInsert, - parse_url( - $this->imagesHelper->getCurrentUrl() . self::TEST_IMAGE_FILE, - PHP_URL_PATH - ) - ); } + + $this->assertEquals($getImageForInsert, $expectedResult); } /** @@ -102,30 +104,35 @@ public function imageDataProvider(): array false, true, 1, + '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' ], [ 'L3Rlc3QtaW1hZ2UuanBn', true, false, 1, + '/pub/media/' . self::TEST_IMAGE_FILE ], [ 'L3Rlc3QtaW1hZ2UuanBn', false, false, 1, + '{{media url="' . self::TEST_IMAGE_FILE . '"}}' ], [ 'L3Rlc3QtaW1hZ2UuanBn', false, true, 2, + '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' ], [ 'L3Rlc3QtaW1hZ2UuanBn', false, true, null, + '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' ], ]; } From afa8c7fd86e7f9f2ee994d361900b5cc8fb236f3 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Mon, 24 Aug 2020 11:53:34 -0500 Subject: [PATCH 48/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Magento/Customer/Model/Customer/Authorization.php | 5 +++++ .../Customer/Model/Customer/AuthorizationComposite.php | 8 ++++++++ .../Customer/Model/Plugin/CustomerAuthorization.php | 8 +++++--- .../Persistent/Model/Customer/Authorization.php | 10 ++++++++++ app/code/Magento/Persistent/etc/webapi_rest/di.xml | 5 +++++ app/code/Magento/Persistent/etc/webapi_soap/di.xml | 5 +++++ 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/Model/Customer/Authorization.php b/app/code/Magento/Customer/Model/Customer/Authorization.php index f4e6d07affc84..f5ddf839597e2 100644 --- a/app/code/Magento/Customer/Model/Customer/Authorization.php +++ b/app/code/Magento/Customer/Model/Customer/Authorization.php @@ -40,6 +40,8 @@ class Authorization implements AuthorizationInterface private $storeManager; /** + * Authorization constructor. + * * @param UserContextInterface $userContext * @param CustomerFactory $customerFactory * @param CustomerResource $customerResource @@ -57,6 +59,9 @@ public function __construct( $this->storeManager = $storeManager; } + /** + * @inheritdoc + */ public function isAllowed($resource, $privilege = null) { if ($resource == AuthorizationService::PERMISSION_SELF diff --git a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php index e643460a32789..790c81056f4ff 100644 --- a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php +++ b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php @@ -19,12 +19,20 @@ class AuthorizationComposite implements AuthorizationInterface */ private $authorizationChecks; + /** + * AuthorizationComposite constructor. + * + * @param array $authorizationChecks + */ public function __construct( array $authorizationChecks ) { $this->authorizationChecks = $authorizationChecks; } + /** + * @inheritdoc + */ public function isAllowed($resource, $privilege = null) { $result = false; diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index 292c22fba512b..f9c16b95ee054 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -33,17 +33,19 @@ public function __construct( } /** + * Verify if to allow customer users to access resources with self permission + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param Authorization $subject * @param Closure $proceed - * @param $resource - * @param null $privilege + * @param string $resource + * @param $privilege * @return bool|mixed */ public function aroundIsAllowed( Authorization $subject, Closure $proceed, - $resource, + string $resource, $privilege = null ) { if ($this->authorizationComposite->isAllowed($resource, $privilege)) { diff --git a/app/code/Magento/Persistent/Model/Customer/Authorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php index 156a55b39991b..0ebda14e215ec 100644 --- a/app/code/Magento/Persistent/Model/Customer/Authorization.php +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -10,6 +10,11 @@ use Magento\Framework\AuthorizationInterface; use Magento\Persistent\Helper\Session as PersistentSession; +/** + * Authorization logic for persistent customers + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class Authorization implements AuthorizationInterface { /** @@ -34,6 +39,11 @@ public function __construct( $this->persistentSession = $persistentSession; } + /** + * @inheritdoc + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ public function isAllowed( $resource, $privilege = null diff --git a/app/code/Magento/Persistent/etc/webapi_rest/di.xml b/app/code/Magento/Persistent/etc/webapi_rest/di.xml index 89504f0471788..5c2c6d0450019 100644 --- a/app/code/Magento/Persistent/etc/webapi_rest/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_rest/di.xml @@ -20,4 +20,9 @@ </argument> </arguments> </type> + <type name="Magento\Persistent\Model\Customer\Authorization"> + <arguments> + <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_soap/di.xml b/app/code/Magento/Persistent/etc/webapi_soap/di.xml index 2a440fff03598..cd1006fa5c73e 100644 --- a/app/code/Magento/Persistent/etc/webapi_soap/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_soap/di.xml @@ -20,4 +20,9 @@ </argument> </arguments> </type> + <type name="Magento\Persistent\Model\Customer\Authorization"> + <arguments> + <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> + </arguments> + </type> </config> From 42e4454fbe9d93dc99f9ded778ba598253982829 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 12 Aug 2020 12:38:12 -0400 Subject: [PATCH 49/95] MC-30502: JSUnit tests incompatible with Node v10 or v12 - Fixed jasmine test failure - Removed unecessary URLs - Fixed nodejs dependency --- .../Magento/Ui/base/js/grid/masonry.test.js | 120 +++++++++--------- package.json.sample | 4 +- 2 files changed, 60 insertions(+), 64 deletions(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js index 2c2cdab2d46da..d1a614d28fc72 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js @@ -6,79 +6,75 @@ /*eslint max-nested-callbacks: 0*/ define([ 'jquery', - 'ko', 'Magento_Ui/js/grid/masonry' -], function ($, ko, Masonry) { +], function ($, Masonry) { 'use strict'; - var Component, - rows, - container = '<div data-id="masonry_grid" id="masonry_grid"><div class="masonry-image-column"></div></div>'; + describe('Magento_Ui/js/grid/masonry', function () { + var Component, + rows, + container = '<div data-id="masonry_grid" id="masonry_grid"><div class="masonry-image-column"></div></div>'; - beforeEach(function () { - rows = [ - { - _rowIndex: 0, - category: {}, - 'category_id': 695, - 'category_name': 'People', - 'comp_url': 'https://stock.adobe.com/Rest/Libraries/Watermarked/Download/327515738/2', - 'content_type': 'image/jpeg', - 'country_name': 'Malaysia', - 'creation_date': '2020-03-02 10:41:51', - 'creator_id': 208217780, - 'creator_name': 'NajmiArif', - height: 3264, - id: 327515738, - 'id_field_name': 'id', - 'is_downloaded': 0, - 'is_licensed_locally': 0, - keywords: [], - 'media_type_id': 1, - overlay: '', - path: '', - 'premium_level_id': 0, - 'thumbnail_240_url': 'https://t4.ftcdn.net/jpg/03/27/51/57/240_F_327515738_n.jpg', - 'thumbnail_500_ur': 'https://as2.ftcdn.net/jpg/03/27/51/57/500_F_327515738_n.jpg', - title: 'Neon effect picture of man wearing medical mask for viral or pandemic disease', - width: 4896 - } + beforeEach(function () { + rows = [ + { + _rowIndex: 0, + category: {}, + 'category_id': 695, + 'category_name': 'People', + 'content_type': 'image/jpeg', + 'country_name': 'Malaysia', + 'creation_date': '2020-03-02 10:41:51', + 'creator_id': 208217780, + 'creator_name': 'NajmiArif', + height: 3264, + id: 327515738, + 'id_field_name': 'id', + 'is_downloaded': 0, + 'is_licensed_locally': 0, + keywords: [], + 'media_type_id': 1, + overlay: '', + path: '', + 'premium_level_id': 0, + title: 'Neon effect picture of man wearing medical mask for viral or pandemic disease', + width: 4896 + } + ]; - ]; - - $(container).appendTo('body'); - - Component = new Masonry({ - defaults: { - rows: ko.observable() - } + $(document.body).append(container); + Component = new Masonry({ + defaults: { + containerId: '#masonry_grid' + } + }); }); - }); - - afterEach(function () { - $('#masonry_grid').remove(); - }); + afterEach(function () { + Component.clear(); + $('#masonry_grid').remove(); + }); - describe('check initComponent', function () { - it('verify setLayoutstyles called and grid iniztilized', function () { - var setlayoutStyles = spyOn(Component, 'setLayoutStyles'); + describe('check initComponent', function () { + it('verify setLayoutstyles called and grid iniztilized', function () { + var setlayoutStyles = spyOn(Component, 'setLayoutStyles'); - expect(Component).toBeDefined(); - Component.containerId = 'masonry_grid'; - Component.initComponent(rows); - Component.rows().forEach(function (image) { - expect(image.styles).toBeDefined(); - expect(image.css).toBeDefined(); + expect(Component).toBeDefined(); + Component.containerId = 'masonry_grid'; + Component.initComponent(rows); + Component.rows().forEach(function (image) { + expect(image.styles).toBeDefined(); + expect(image.css).toBeDefined(); + }); + expect(setlayoutStyles).toHaveBeenCalled(); }); - expect(setlayoutStyles).toHaveBeenCalled(); - }); - it('verify events triggered', function () { - var setLayoutStyles = spyOn(Component, 'setLayoutStyles'); + it('verify events triggered', function () { + var setLayoutStyles = spyOn(Component, 'setLayoutStyles'); - Component.initComponent(rows); - window.dispatchEvent(new Event('resize')); - expect(setLayoutStyles).toHaveBeenCalled(); + Component.initComponent(rows); + window.dispatchEvent(new Event('resize')); + expect(setLayoutStyles).toHaveBeenCalled(); + }); }); }); }); diff --git a/package.json.sample b/package.json.sample index 4dea6d7b945f5..93fe72afbd24a 100644 --- a/package.json.sample +++ b/package.json.sample @@ -18,7 +18,7 @@ "grunt-contrib-connect": "~1.0.2", "grunt-contrib-cssmin": "~2.2.1", "grunt-contrib-imagemin": "~2.0.1", - "grunt-contrib-jasmine": "~1.1.0", + "grunt-contrib-jasmine": "~1.2.0", "grunt-contrib-less": "~1.4.1", "grunt-contrib-watch": "~1.0.0", "grunt-eslint": "~20.1.0", @@ -39,4 +39,4 @@ "time-grunt": "~1.4.0", "underscore": "~1.8.0" } -} \ No newline at end of file +} From 708c6acdd9cbf6f231852d7c8631e157c859ca96 Mon Sep 17 00:00:00 2001 From: rrego6 <rrego@adobe.com> Date: Thu, 20 Aug 2020 15:11:32 -0400 Subject: [PATCH 50/95] MC-30502: JSUnit tests incompatible with Node v10 or v12 - added placeholder for urls --- .../tests/app/code/Magento/Ui/base/js/grid/masonry.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js index d1a614d28fc72..7f7d0c5f9dd2a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js @@ -22,6 +22,7 @@ define([ category: {}, 'category_id': 695, 'category_name': 'People', + 'comp_url': 'url', 'content_type': 'image/jpeg', 'country_name': 'Malaysia', 'creation_date': '2020-03-02 10:41:51', @@ -37,6 +38,8 @@ define([ overlay: '', path: '', 'premium_level_id': 0, + 'thumbnail_240_url': 'url', + 'thumbnail_500_ur': 'url', title: 'Neon effect picture of man wearing medical mask for viral or pandemic disease', width: 4896 } From 32b30e570fc747f7c1b63305be9d9dbff10cbb79 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Mon, 24 Aug 2020 15:15:52 -0500 Subject: [PATCH 51/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Customer/Model/Plugin/CustomerAuthorization.php | 2 +- app/code/Magento/Persistent/etc/di.xml | 10 ++++++++++ app/code/Magento/Persistent/etc/webapi_rest/di.xml | 5 ----- app/code/Magento/Persistent/etc/webapi_soap/di.xml | 5 ----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index f9c16b95ee054..c3de3af812670 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -39,7 +39,7 @@ public function __construct( * @param Authorization $subject * @param Closure $proceed * @param string $resource - * @param $privilege + * @param null $privilege * @return bool|mixed */ public function aroundIsAllowed( diff --git a/app/code/Magento/Persistent/etc/di.xml b/app/code/Magento/Persistent/etc/di.xml index f49d4361acb52..fd1c97fae66d9 100644 --- a/app/code/Magento/Persistent/etc/di.xml +++ b/app/code/Magento/Persistent/etc/di.xml @@ -12,4 +12,14 @@ <type name="Magento\Customer\CustomerData\Customer"> <plugin name="section_data" type="Magento\Persistent\Model\Plugin\CustomerData" /> </type> + <type name="Magento\Persistent\Model\Customer\Authorization"> + <arguments> + <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> + </arguments> + </type> + <type name="Magento\Persistent\Helper\Session"> + <arguments> + <argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_rest/di.xml b/app/code/Magento/Persistent/etc/webapi_rest/di.xml index 5c2c6d0450019..89504f0471788 100644 --- a/app/code/Magento/Persistent/etc/webapi_rest/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_rest/di.xml @@ -20,9 +20,4 @@ </argument> </arguments> </type> - <type name="Magento\Persistent\Model\Customer\Authorization"> - <arguments> - <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> - </arguments> - </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_soap/di.xml b/app/code/Magento/Persistent/etc/webapi_soap/di.xml index cd1006fa5c73e..2a440fff03598 100644 --- a/app/code/Magento/Persistent/etc/webapi_soap/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_soap/di.xml @@ -20,9 +20,4 @@ </argument> </arguments> </type> - <type name="Magento\Persistent\Model\Customer\Authorization"> - <arguments> - <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> - </arguments> - </type> </config> From 2c0c2805d516263a771f0737c3e3db5c0f817cae Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Mon, 24 Aug 2020 17:25:58 -0500 Subject: [PATCH 52/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Magento/Customer/Model/Plugin/CustomerAuthorization.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index c3de3af812670..65bf9843e44fd 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -39,7 +39,7 @@ public function __construct( * @param Authorization $subject * @param Closure $proceed * @param string $resource - * @param null $privilege + * @param mixed $privilege * @return bool|mixed */ public function aroundIsAllowed( From d93f3d2b67a6d6777603927fd40bac604a1b1094 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 25 Aug 2020 13:17:21 +0100 Subject: [PATCH 53/95] magento/magento2#29677: Fixed static tests --- .../Adminhtml/Wysiwyg/Images/OnInsert.php | 56 +++++++------ .../Wysiwyg/Images/GetInsertImageContent.php | 7 +- .../Images/GetInsertImageContentTest.php | 80 +++++++++---------- 3 files changed, 66 insertions(+), 77 deletions(-) rename {app/code/Magento/Cms/Test/Integration => dev/tests/integration/testsuite/Magento/Cms}/Model/Wysiwyg/Images/GetInsertImageContentTest.php (50%) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index f62d3debc74bd..e9a2431e2393d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -1,64 +1,62 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; -class OnInsert extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +use Magento\Backend\App\Action\Context; +use Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\RawFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Registry; + +class OnInsert extends Images implements HttpGetActionInterface { /** - * @var \Magento\Framework\Controller\Result\RawFactory + * @var RawFactory */ protected $resultRawFactory; /** - * @var \Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent + * @var GetInsertImageContent */ protected $getInsertImageContent; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory - * @param \Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent $getInsertImageContent + * @param Context $context + * @param Registry $coreRegistry + * @param RawFactory $resultRawFactory + * @param GetInsertImageContent $getInsertImageContent */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, - ?\Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent $getInsertImageContent = null + Context $context, + Registry $coreRegistry, + RawFactory $resultRawFactory, + ?GetInsertImageContent $getInsertImageContent = null ) { $this->resultRawFactory = $resultRawFactory; parent::__construct($context, $coreRegistry); $this->getInsertImageContent = $getInsertImageContent ?: $this->_objectManager - ->get(\Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent::class); + ->get(GetInsertImageContent::class); } /** - * Fire when select image + * Return a content (just a link or an html block) for inserting image to the content * - * @return \Magento\Framework\Controller\ResultInterface + * @return ResultInterface */ public function execute() { $data = $this->getRequest()->getParams(); - - $fileName = $data['filename']; - $forceStaticPath = $data['force_static_path']; - $renderAsTag = $data['as_is']; - $storeId = isset($data['store_id']) ? (int) $data['store_id'] : null; - - /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ - $resultRaw = $this->resultRawFactory->create(); - - return $resultRaw->setContents( + return $this->resultRawFactory->create()->setContents( $this->getInsertImageContent->execute( - $fileName, - $forceStaticPath, - $renderAsTag, - $storeId + $data['filename'], + $data['force_static_path'], + $data['as_is'], + isset($data['store']) ? (int) $data['store'] : null ) ); } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php index e6f59c484bfc2..3531bf7569e38 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -28,7 +28,7 @@ public function __construct(ImagesHelper $imagesHelper) } /** - * Prepare Image Contents for Insert + * Create a content (just a link or an html block) for inserting image to the content * * @param string $encodedFilename * @param bool $forceStaticPath @@ -48,10 +48,7 @@ public function execute( if ($forceStaticPath) { // phpcs:ignore Magento2.Functions.DiscouragedFunction - return parse_url( - $this->imagesHelper->getCurrentUrl() . $filename, - PHP_URL_PATH - ); + return parse_url($this->imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); } return $this->imagesHelper->getImageHtmlDeclaration($filename, $renderAsTag); diff --git a/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php similarity index 50% rename from app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php rename to dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php index fb026f6b4292a..076a669f3f8ad 100644 --- a/app/code/Magento/Cms/Test/Integration/Model/Wysiwyg/Images/GetInsertImageContentTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php @@ -6,19 +6,16 @@ declare(strict_types=1); -namespace Magento\Cms\Test\Integration\Model\Wysiwyg\Images; +namespace Magento\Cms\Model\Wysiwyg\Images; -use Magento\Backend\Helper\Data as BackendHelper; +use Magento\Backend\Model\UrlInterface; use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; -use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; use Magento\Framework\Url\EncoderInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; class GetInsertImageContentTest extends TestCase { - private const TEST_IMAGE_FILE = '/test-image.jpg'; - /** * @var GetInsertImageContent */ @@ -35,9 +32,9 @@ class GetInsertImageContentTest extends TestCase private $urlEncoder; /** - * @var BackendHelper + * @var UrlInterface */ - protected $_backendData; + protected $url; /** * @inheritdoc @@ -47,48 +44,45 @@ protected function setUp(): void $this->getInsertImageContent = Bootstrap::getObjectManager()->get(GetInsertImageContent::class); $this->imagesHelper = Bootstrap::getObjectManager()->get(ImagesHelper::class); $this->urlEncoder = Bootstrap::getObjectManager()->get(EncoderInterface::class); - $this->_backendData = Bootstrap::getObjectManager()->get(BackendHelper::class); + $this->url = Bootstrap::getObjectManager()->get(UrlInterface::class); } /** * Test for GetInsertImageContent::execute * * @dataProvider imageDataProvider - * @param string $encodedFilename + * @param string $filename * @param bool $forceStaticPath * @param bool $renderAsTag * @param int|null $storeId - * @param string|null $expectedResult + * @param string $expectedResult */ public function testExecute( - string $encodedFilename, + string $filename, bool $forceStaticPath, bool $renderAsTag, - ?int $storeId = null, - ?string $expectedResult = null + ?int $storeId, + string $expectedResult ): void { - $getImageForInsert = $this->getInsertImageContent->execute( - $encodedFilename, - $forceStaticPath, - $renderAsTag, - $storeId - ); - - if (!$forceStaticPath && !$renderAsTag) { - if (!$this->imagesHelper->isUsingStaticUrlsAllowed()) { - - $encodedDirective = $this->urlEncoder->encode($expectedResult); - $expectedResult = $this->_backendData->getUrl( - 'cms/wysiwyg/directive', - [ - '___directive' => $encodedDirective, - '_escape_params' => false, - ] - ); - } + if (!$forceStaticPath && !$renderAsTag && !$this->imagesHelper->isUsingStaticUrlsAllowed()) { + $expectedResult = $this->url->getUrl( + 'cms/wysiwyg/directive', + [ + '___directive' => $this->urlEncoder->encode($expectedResult), + '_escape_params' => false + ] + ); } - $this->assertEquals($getImageForInsert, $expectedResult); + $this->assertEquals( + $expectedResult, + $this->getInsertImageContent->execute( + $this->imagesHelper->idEncode($filename), + $forceStaticPath, + $renderAsTag, + $storeId + ) + ); } /** @@ -100,39 +94,39 @@ public function imageDataProvider(): array { return [ [ - 'L3Rlc3QtaW1hZ2UuanBn', + 'test-image.jpg', false, true, 1, - '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' + '<img src="{{media url="test-image.jpg"}}" alt="" />' ], [ - 'L3Rlc3QtaW1hZ2UuanBn', + 'catalog/category/test-image.jpg', true, false, 1, - '/pub/media/' . self::TEST_IMAGE_FILE + '/pub/media/catalog/category/test-image.jpg' ], [ - 'L3Rlc3QtaW1hZ2UuanBn', + 'test-image.jpg', false, false, 1, - '{{media url="' . self::TEST_IMAGE_FILE . '"}}' + '{{media url="test-image.jpg"}}' ], [ - 'L3Rlc3QtaW1hZ2UuanBn', + '/test-image.jpg', false, true, 2, - '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' + '<img src="{{media url="/test-image.jpg"}}" alt="" />' ], [ - 'L3Rlc3QtaW1hZ2UuanBn', + 'test-image.jpg', false, true, null, - '<img src="{{media url="' . self::TEST_IMAGE_FILE . '"}}" alt="" />' + '<img src="{{media url="test-image.jpg"}}" alt="" />' ], ]; } From 33921dd7898e67b606091627d9fbb7a408d1f694 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 25 Aug 2020 09:10:58 -0500 Subject: [PATCH 54/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- app/code/Magento/Customer/Model/Customer/Authorization.php | 2 +- .../Magento/Customer/Model/Customer/AuthorizationComposite.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Customer/Model/Customer/Authorization.php b/app/code/Magento/Customer/Model/Customer/Authorization.php index f5ddf839597e2..027eeb0dd581b 100644 --- a/app/code/Magento/Customer/Model/Customer/Authorization.php +++ b/app/code/Magento/Customer/Model/Customer/Authorization.php @@ -15,7 +15,7 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Class to invalidate user credentials + * Checks if customer is logged in and authorized in the current store */ class Authorization implements AuthorizationInterface { diff --git a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php index 790c81056f4ff..c0d4651a2e0f1 100644 --- a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php +++ b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php @@ -22,7 +22,7 @@ class AuthorizationComposite implements AuthorizationInterface /** * AuthorizationComposite constructor. * - * @param array $authorizationChecks + * @param AuthorizationInterface[] $authorizationChecks */ public function __construct( array $authorizationChecks From 272dcf6bb42123d7b29b46d90e57172b0f61de8f Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 25 Aug 2020 09:52:56 -0500 Subject: [PATCH 55/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Magento/Customer/Model/Customer/Authorization.php | 8 ++++---- .../Customer/Model/Customer/AuthorizationComposite.php | 1 + .../Magento/Persistent/Model/Customer/Authorization.php | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Customer/Model/Customer/Authorization.php b/app/code/Magento/Customer/Model/Customer/Authorization.php index 027eeb0dd581b..5df3dbc51b732 100644 --- a/app/code/Magento/Customer/Model/Customer/Authorization.php +++ b/app/code/Magento/Customer/Model/Customer/Authorization.php @@ -4,6 +4,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Model\Customer; @@ -64,7 +65,7 @@ public function __construct( */ public function isAllowed($resource, $privilege = null) { - if ($resource == AuthorizationService::PERMISSION_SELF + if ($resource === AuthorizationService::PERMISSION_SELF && $this->userContext->getUserId() && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER ) { @@ -72,9 +73,8 @@ public function isAllowed($resource, $privilege = null) $this->customerResource->load($customer, $this->userContext->getUserId()); $currentStoreId = $this->storeManager->getStore()->getId(); $sharedStoreIds = $customer->getSharedStoreIds(); - if (in_array($currentStoreId, $sharedStoreIds)) { - return true; - } + + return in_array($currentStoreId, $sharedStoreIds); } return false; diff --git a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php index c0d4651a2e0f1..716719470796e 100644 --- a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php +++ b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php @@ -4,6 +4,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Model\Customer; diff --git a/app/code/Magento/Persistent/Model/Customer/Authorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php index 0ebda14e215ec..6d8859a30fd96 100644 --- a/app/code/Magento/Persistent/Model/Customer/Authorization.php +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Persistent\Model\Customer; From 133e8531ed9422285a7251a2424f155eba437ff5 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 25 Aug 2020 15:58:18 +0100 Subject: [PATCH 56/95] Corrected controller interface --- .../Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index e9a2431e2393d..1266dcc7c035c 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -8,12 +8,12 @@ use Magento\Backend\App\Action\Context; use Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; -use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\Result\RawFactory; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Registry; -class OnInsert extends Images implements HttpGetActionInterface +class OnInsert extends Images implements HttpPostActionInterface { /** * @var RawFactory From 00ae2382e1fc813cf53035e73455d41e65388128 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 25 Aug 2020 10:00:54 -0500 Subject: [PATCH 57/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Magento/Customer/Model/Plugin/CustomerAuthorization.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index 65bf9843e44fd..271d8f795d6f6 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -40,7 +40,7 @@ public function __construct( * @param Closure $proceed * @param string $resource * @param mixed $privilege - * @return bool|mixed + * @return bool */ public function aroundIsAllowed( Authorization $subject, From 252e59659488dba0090f182a5eed16eac5db5bed Mon Sep 17 00:00:00 2001 From: Leonid Poluianov <poluyano@adobe.com> Date: Mon, 24 Aug 2020 16:15:00 -0500 Subject: [PATCH 58/95] MC-36959: Remove "compareArraysRecursively" logic duplication --- .../TestCase/GraphQlAbstract.php | 57 ------------------- .../BundleProductMultipleOptionsTest.php | 18 +++++- 2 files changed, 17 insertions(+), 58 deletions(-) diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index 7f67c8c9ca8df..3de18a932f2cd 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -184,61 +184,4 @@ protected function assertResponseFields($actualResponse, $assertionMap) ); } } - - /** - * 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/GraphQl/Bundle/BundleProductMultipleOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php index 3409b5e3af1af..77c4d5b84e72e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php @@ -7,6 +7,8 @@ namespace Magento\GraphQl\Bundle; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CompareArraysRecursively; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -14,6 +16,20 @@ */ class BundleProductMultipleOptionsTest extends GraphQlAbstract { + /** + * @var CompareArraysRecursively + */ + private $compareArraysRecursively; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + } + /** * @magentoApiDataFixture Magento/Bundle/_files/product_with_multiple_options.php * @param array $bundleProductDataProvider @@ -85,7 +101,7 @@ private function assertBundleProduct(array $response, array $bundleProductDataPr $productItems = $response['products']['items']; foreach ($bundleProductDataProvider as $key => $data) { - $diff = $this->compareArraysRecursively($data, $productItems[$key]); + $diff = $this->compareArraysRecursively->execute($data, $productItems[$key]); self::assertEquals([], $diff, "Actual response doesn't equal to expected data"); } } From fc039c3b4116ea0b08bbaf6a6c65b2e08d267bd0 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 25 Aug 2020 18:13:32 +0300 Subject: [PATCH 59/95] Set path column as text type --- app/code/Magento/MediaGallery/etc/db_schema.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGallery/etc/db_schema.xml b/app/code/Magento/MediaGallery/etc/db_schema.xml index 1001737daa8a7..a1662ba63f199 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema.xml +++ b/app/code/Magento/MediaGallery/etc/db_schema.xml @@ -8,7 +8,7 @@ <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="media_gallery_asset" resource="default" engine="innodb" comment="Media Gallery Asset"> <column xsi:type="int" name="id" unsigned="true" nullable="false" identity="true" comment="Entity ID"/> - <column xsi:type="varchar" name="path" length="255" nullable="true" comment="Path"/> + <column xsi:type="text" name="path" nullable="true" comment="Path"/> <column xsi:type="varchar" name="title" length="255" nullable="true" comment="Title"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="varchar" name="source" length="255" nullable="true" comment="Source"/> From 9d57aa0abf59b31821ef628284f93129db89db9f Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 25 Aug 2020 18:50:58 +0300 Subject: [PATCH 60/95] Skip segment reader if there is any kind of exception --- .../MediaGalleryMetadata/Model/File/ExtractMetadata.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php b/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php index 00f2b07f5bb81..f5efd25bca041 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php @@ -7,7 +7,6 @@ namespace Magento\MediaGalleryMetadata\Model\File; -use Magento\Framework\Exception\LocalizedException; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; @@ -90,7 +89,12 @@ private function readSegments(FileInterface $file): MetadataInterface ); } - $data = $segmentReader->execute($file); + try { + $data = $segmentReader->execute($file); + } catch (\Exception $exception) { + continue; + } + $title = !empty($data->getTitle()) ? $data->getTitle() : $title; $description = !empty($data->getDescription()) ? $data->getDescription() : $description; From b8f103ba1765ce35905fc647b02e67d1732a8d85 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 25 Aug 2020 18:05:16 +0100 Subject: [PATCH 61/95] magento/magento2#29742: Removed path unique constraint --- app/code/Magento/MediaGallery/etc/db_schema.xml | 3 --- app/code/Magento/MediaGallery/etc/db_schema_whitelist.json | 1 - 2 files changed, 4 deletions(-) diff --git a/app/code/Magento/MediaGallery/etc/db_schema.xml b/app/code/Magento/MediaGallery/etc/db_schema.xml index a1662ba63f199..1a9b0dc96a655 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema.xml +++ b/app/code/Magento/MediaGallery/etc/db_schema.xml @@ -25,9 +25,6 @@ <index referenceId="MEDIA_GALLERY_ID" indexType="btree"> <column name="id"/> </index> - <constraint xsi:type="unique" referenceId="MEDIA_GALLERY_ID_PATH_TITLE_CONTENT_TYPE_WIDTH_HEIGHT"> - <column name="path"/> - </constraint> <index referenceId="MEDIA_GALLERY_ASSET_TITLE" indexType="fulltext"> <column name="title"/> </index> diff --git a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json index b32dfbf082175..e958d630b7e3f 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json +++ b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json @@ -20,7 +20,6 @@ "MEDIA_GALLERY_ASSET_TITLE": true }, "constraint": { - "MEDIA_GALLERY_ID_PATH_TITLE_CONTENT_TYPE_WIDTH_HEIGHT": true, "PRIMARY": true, "MEDIA_GALLERY_ASSET_PATH": true } From 519f4ce5bb9e33bf4b0fa9f7a0a43fa328a745d8 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 26 Aug 2020 01:07:41 +0800 Subject: [PATCH 62/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - modified ui component and mftf files, additional action group for search by category name on category grid page --- ...egoryGridPageByCategoryNameActionGroup.xml | 23 +++++++++++ ...dminCategoryGridPageDetailsActionGroup.xml | 40 ++++++++++++++++--- ...diaGalleryCatalogUiCategoryGridSection.xml | 13 +++--- ...eryCatalogUiVerifyCategoryGridPageTest.xml | 5 +++ .../Ui/Component/Listing/Columns/InMenu.php | 4 +- .../Ui/Component/Listing/Columns/IsActive.php | 4 +- .../media_gallery_category_listing.xml | 2 - ...ancedMediaGalleryVerifyAssetFilterTest.xml | 11 +++-- 8 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml new file mode 100644 index 0000000000000..02635f8e379cf --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml @@ -0,0 +1,23 @@ +<?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="AdminSearchCategoryGridPageByCategoryNameActionGroup"> + <annotations> + <description>Fills 'Search by category name' on Category Grid page. Clicks on Submit Search.</description> + </annotations> + <arguments> + <argument name="categoryName"/> + </arguments> + + <conditionalClick selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" dependentSelector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <fillField selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.search}}" userInput="{{categoryName}}" stepKey="fillKeywordSearchField"/> + <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.submitSearch}}" stepKey="clickKeywordSearch"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml index f86e04ad7c4f2..b698fa05600d7 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml @@ -17,10 +17,40 @@ </annotations> <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(imageName)}}" stepKey="assertImageColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.path(category.name)}}" stepKey="assertPathColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name(category.name)}}" stepKey="assertNameColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('0')}}" stepKey="assertProductsColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.inMenu(category.include_in_menu)}}" stepKey="assertInMenuColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.enabled(category.is_active)}}" stepKey="assertEnabledColumn"/> + + <!--Name Column--> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Name')}}" stepKey="grabNameColumnValue"/> + <assertEquals stepKey="assertNameColumn"> + <expectedResult type="string">$$category.name$$</expectedResult> + <actualResult type="variable">grabNameColumnValue</actualResult> + </assertEquals> + + <!--Path Column--> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Path')}}" stepKey="grabPathColumnValue"/> + <assertStringContainsString stepKey="assertPathColumn"> + <expectedResult type="string">$$category.name$$</expectedResult> + <actualResult type="variable">grabPathColumnValue</actualResult> + </assertStringContainsString> + + <!--Products Column--> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Products')}}" stepKey="grabProductsColumnValue"/> + <assertEquals stepKey="assertProductsColumn"> + <expectedResult type="string">0</expectedResult> + <actualResult type="variable">grabProductsColumnValue</actualResult> + </assertEquals> + + <!--In Menu Column--> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('In Menu')}}" stepKey="grabInMenuColumnValue"/> + <assertEquals stepKey="assertInMenuColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabInMenuColumnValue</actualResult> + </assertEquals> + + <!--Enabled Column--> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Enabled')}}" stepKey="grabEnabledColumnValue"/> + <assertEquals stepKey="assertEnabledColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabEnabledColumnValue</actualResult> + </assertEquals> </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 a5ed4f54eca0f..f35e3c9b76a00 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,14 +9,15 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"> + <!--Search by category name element--> + <element name="search" type="input" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] input[placeholder='Search by category name']"/> + <element name="submitSearch" type="button" selector=".data-grid-search-control-wrap > button.action-submit" timeout="30"/> + <!--Active filter element--> + <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> <element name="activeFilterPlaceholder" type="text" selector="//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> + <!--Category Grid Page Columns--> <element name="image" type="text" selector="//tr//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//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Path')]/preceding-sibling::th) +1 ]//*[contains(text(), '{{categoryName}}')]" parameterized="true"/> - <element name="name" type="text" selector="//tr//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//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//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//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'In Menu')]/preceding-sibling::th) +1 ]//span[contains(@class, '{{inMenuValue}}')]" parameterized="true"/> - <element name="enabled" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Enabled')]/preceding-sibling::th) +1 ]//span[contains(@class, '{{enabledValue}}')]" parameterized="true"/> + <element name="columnValue" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{columnName}}')]/preceding-sibling::th) +1 ]//div" parameterized="true"/> <element name="edit" type="button" selector="//tr[td//text()[contains(., '{{categoryName}}')]]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{actionButton}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml index b2668b59fb8a1..6ca2500723400 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml @@ -26,6 +26,11 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> + + <actionGroup ref="AdminSearchCategoryGridPageByCategoryNameActionGroup" stepKey="searchByCategoryName"> + <argument name="categoryName" value="$$category.name$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> <argument name="category" value="$$category$$"/> </actionGroup> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php index 00b5dd87e8cf1..fe4720b4a3e60 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = '<span class="1">Yes</span>'; + $item[$fieldName] = 'Yes'; } else { - $item[$fieldName] = '<span class="0">No</span>'; + $item[$fieldName] = 'No'; } } } diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php index 48464d6ffdcbc..c6f20c937d5b3 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php @@ -24,9 +24,9 @@ public function prepareDataSource(array $dataSource) $fieldName = $this->getData('name'); foreach ($dataSource['data']['items'] as & $item) { if (isset($item[$fieldName]) && $item[$fieldName] == 1) { - $item[$fieldName] = '<span class="1">Yes</span>'; + $item[$fieldName] = 'Yes'; } else { - $item[$fieldName] = '<span class="0">No</span>'; + $item[$fieldName] = 'No'; } } } 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 94ef605f856c7..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 @@ -170,13 +170,11 @@ <column name="include_in_menu" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\InMenu"> <settings> <label translate="true">In Menu</label> - <bodyTmpl>ui/grid/cells/html</bodyTmpl> </settings> </column> <column name="is_active" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\IsActive"> <settings> <label translate="true">Enabled</label> - <bodyTmpl>ui/grid/cells/html</bodyTmpl> </settings> </column> <actionsColumn name="actions" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\CategoryActions" sortOrder="1000"> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml index bd7e4fcf7a9a2..1c2a6d7ed1d01 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml @@ -65,6 +65,7 @@ <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveCategory"/> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryGridPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="firstResetAdminDataGridToDefaultView"/> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridExpandFilterActionGroup" stepKey="expandFilters"/> <actionGroup ref="AdminEnhancedMediaGallerySelectUsedInFilterActionGroup" stepKey="setUsedInFilter"> <argument name="filterName" value="Asset"/> @@ -72,9 +73,11 @@ </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridApplyFiltersActionGroup" stepKey="applyFilters"/> - <actionGroup ref="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup" stepKey="assertCategoryInGrid"> - <argument name="categoryName" value="$$category.name$$"/> - </actionGroup> - <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Name')}}" stepKey="grabCategoryNameFromGrid"/> + <assertEquals stepKey="assertEqualCategoryName"> + <expectedResult type="string">$$category.name$$</expectedResult> + <actualResult type="variable">grabCategoryNameFromGrid</actualResult> + </assertEquals> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> </test> </tests> From 3c10b6997c08e809942622e83a4df8f4e01500e5 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Wed, 26 Aug 2020 20:39:32 +0800 Subject: [PATCH 63/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - introduced new ActionGroup and modified related mftf files --- ...goryGridPageNumberOfRecordsActionGroup.xml | 24 +++++++++++++++++++ ...diaGalleryCatalogUiCategoryGridSection.xml | 4 +++- ...alogUiVerifyUsedInLinkCategoryGridTest.xml | 7 ++++-- ...tCategoryNameInCategoryGridActionGroup.xml | 21 ---------------- 4 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml delete mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml new file mode 100644 index 0000000000000..66f5a74d17d94 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml @@ -0,0 +1,24 @@ +<?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="AssertAdminCategoryGridPageNumberOfRecordsActionGroup"> + <arguments> + <argument name="numberOfRecords" type="string"/> + </arguments> + <annotations> + <description>Assert the number of records in the category grid page.</description> + </annotations> + + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.recordsLabel}}" stepKey="grabRecordsLabel"/> + <assertEquals stepKey="assertStringIsEqual"> + <expectedResult type="string">{{numberOfRecords}}</expectedResult> + <actualResult type="variable">grabRecordsLabel</actualResult> + </assertEquals> + </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 f35e3c9b76a00..1d5f4e7cad5ea 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -12,7 +12,9 @@ <!--Search by category name element--> <element name="search" type="input" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] input[placeholder='Search by category name']"/> <element name="submitSearch" type="button" selector=".data-grid-search-control-wrap > button.action-submit" timeout="30"/> - <!--Active filter element--> + <!--Records element--> + <element name="recordsLabel" type="text" selector=".admin__data-grid-header .admin__control-support-text"/> + <!--Filter element--> <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> <element name="activeFilterPlaceholder" type="text" selector="//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> <!--Category Grid Page Columns--> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml index 7e0fa6c477c45..4877d0caf88f4 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -57,8 +57,11 @@ <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterApplied"> <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> </actionGroup> - <actionGroup ref="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup" stepKey="assertCategoryInGrid"> - <argument name="categoryName" value="$$category.name$$"/> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> + <argument name="category" value="$$category$$"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml deleted file mode 100644 index cc4632bbdb86e..0000000000000 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?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="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup"> - <annotations> - <description>Asserts category name in category grid page</description> - </annotations> - <arguments> - <argument name="categoryName" type="string"/> - </arguments> - - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name(categoryName)}}" stepKey="assertNameColumn"/> - </actionGroup> -</actionGroups> From 748f623adda944c09a91a6b338d9f4aa22a0d8d2 Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Wed, 26 Aug 2020 22:01:08 +0800 Subject: [PATCH 64/95] magento/adobe-stock-integration#1784: Remove area emulation from media-content:sync command - remove area emulation --- .../Console/Command/Synchronize.php | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php b/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php index 55f99697c289b..e591b4f2339b1 100644 --- a/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php +++ b/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php @@ -7,8 +7,6 @@ namespace Magento\MediaContentSynchronization\Console\Command; -use Magento\Framework\App\Area; -use Magento\Framework\App\State; use Magento\Framework\Console\Cli; use Magento\MediaContentSynchronizationApi\Api\SynchronizeInterface; use Symfony\Component\Console\Command\Command; @@ -25,21 +23,13 @@ class Synchronize extends Command */ private $synchronizeContent; - /** - * @var State $state - */ - private $state; - /** * @param SynchronizeInterface $synchronizeContent - * @param State $state */ public function __construct( - SynchronizeInterface $synchronizeContent, - State $state + SynchronizeInterface $synchronizeContent ) { $this->synchronizeContent = $synchronizeContent; - $this->state = $state; parent::__construct(); } @@ -58,12 +48,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Synchronizing content with assets...'); - $this->state->emulateAreaCode( - Area::AREA_ADMINHTML, - function () { - $this->synchronizeContent->execute(); - } - ); + $this->synchronizeContent->execute(); $output->writeln('Completed content synchronization.'); return Cli::RETURN_SUCCESS; } From 4fd4dd266edc48de8d1538c75a3256da26e7dd99 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Wed, 26 Aug 2020 09:37:33 -0500 Subject: [PATCH 65/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Unit/Model/Customer/AuthorizationTest.php | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php new file mode 100644 index 0000000000000..4ee5309fcd9e3 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\Test\Unit\Model\Customer; + +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Persistent\Helper\Session as PersistentSession; +use Magento\Persistent\Model\Customer\Authorization; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * A test class for the persistent customers authorization + * + * Unit tests for \Magento\Persistent\Model\Customer\Authorization class. + */ +class AuthorizationTest extends TestCase +{ + /** + * @var PersistentSession|MockObject + */ + private $persistentSessionMock; + + /** + * @var Authorization + */ + private $authorization; + + /** + * @var CustomerSession|MockObject + */ + private $customerSessionMock; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->persistentSessionMock = $this->getMockBuilder(PersistentSession::class) + ->onlyMethods(['isPersistent']) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerSessionMock = $this->getMockBuilder(CustomerSession::class) + ->onlyMethods(['isLoggedIn']) + ->disableOriginalConstructor() + ->getMock(); + + $this->authorization = new Authorization( + $this->customerSessionMock, + $this->persistentSessionMock + ); + } + + /** + * Validate if isAuthorized() will return proper permission value for logged in/ out persistent customers + * + * @dataProvider persistentLoggedInCombinations + * @param bool $isPersistent + * @param bool $isLoggedIn + * @param bool $isAllowedExpectation + */ + public function testIsAuthorized( + bool $isPersistent, + bool $isLoggedIn, + bool $isAllowedExpectation + ): void { + $this->persistentSessionMock->method('isPersistent')->willReturn($isPersistent); + $this->customerSessionMock->method('isLoggedIn')->willReturn($isLoggedIn); + $isAllowedResult = $this->authorization->isAllowed('self'); + + $this->assertEquals($isAllowedExpectation, $isAllowedResult); + } + + /** + * @return array + */ + public function persistentLoggedInCombinations(): array + { + return [ + [ + true, + false, + false + ], + [ + true, + true, + true + ], + [ + false, + false, + true + ], + ]; + } +} From bb99950661edae2099bcb54bdf23e3222381f02a Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Wed, 26 Aug 2020 10:20:39 -0500 Subject: [PATCH 66/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Unit/Model/Customer/AuthorizationTest.php | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php index 4ee5309fcd9e3..049014807190c 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php @@ -9,9 +9,10 @@ use Magento\Customer\Model\Session as CustomerSession; use Magento\Persistent\Helper\Session as PersistentSession; -use Magento\Persistent\Model\Customer\Authorization; +use Magento\Persistent\Model\Customer\Authorization as PersistentAuthorization; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Magento\Customer\Model\Customer\AuthorizationComposite as CustomerAuthorizationComposite; /** * A test class for the persistent customers authorization @@ -26,15 +27,20 @@ class AuthorizationTest extends TestCase private $persistentSessionMock; /** - * @var Authorization + * @var PersistentAuthorization */ - private $authorization; + private $persistentCustomerAuthorization; /** * @var CustomerSession|MockObject */ private $customerSessionMock; + /** + * @var CustomerAuthorizationComposite + */ + private $customerAuthorizationComposite; + /** * @inheritdoc */ @@ -50,10 +56,14 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMock(); - $this->authorization = new Authorization( + $this->persistentCustomerAuthorization = new PersistentAuthorization( $this->customerSessionMock, $this->persistentSessionMock ); + + $this->customerAuthorizationComposite = new CustomerAuthorizationComposite( + [$this->persistentCustomerAuthorization] + ); } /** @@ -71,7 +81,7 @@ public function testIsAuthorized( ): void { $this->persistentSessionMock->method('isPersistent')->willReturn($isPersistent); $this->customerSessionMock->method('isLoggedIn')->willReturn($isLoggedIn); - $isAllowedResult = $this->authorization->isAllowed('self'); + $isAllowedResult = $this->customerAuthorizationComposite->isAllowed('self'); $this->assertEquals($isAllowedExpectation, $isAllowedResult); } From cbc1023155f37b4d5189b2d44c5dff80bd79bfb6 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Wed, 26 Aug 2020 10:23:14 -0500 Subject: [PATCH 67/95] MC-36647: Order can be placed as a customer after session cookie expiration with Persistent Cart enabled --- .../Unit/Model/Customer/AuthorizationTest.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php index 049014807190c..d2abafc7e5ecf 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php @@ -92,21 +92,21 @@ public function testIsAuthorized( public function persistentLoggedInCombinations(): array { return [ - [ - true, - false, - false - ], - [ - true, - true, - true - ], - [ - false, - false, - true - ], + [ + true, + false, + false + ], + [ + true, + true, + true + ], + [ + false, + false, + true + ], ]; } } From 2279d2c66cd589c0b84be652c8159ba2a950c6ef Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 27 Aug 2020 00:26:37 +0800 Subject: [PATCH 68/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - requested changes --- ...egoryGridPageByCategoryNameActionGroup.xml | 5 +-- ...dminCategoryGridPageDetailsActionGroup.xml | 29 +---------------- ...CategoryGridPageImageColumnActionGroup.xml | 20 ++++++++++++ ...goryGridPageNumberOfRecordsActionGroup.xml | 4 +-- ...roductsInMenuEnabledColumnsActionGroup.xml | 31 +++++++++++++++++++ ...nMediaGalleryCatalogUiCategoryGridPage.xml | 1 + ...leryCatalogUiCategoryGridSearchSection.xml | 16 ++++++++++ ...diaGalleryCatalogUiCategoryGridSection.xml | 9 +----- ...eryCatalogUiVerifyCategoryGridPageTest.xml | 8 +++-- ...alogUiVerifyUsedInLinkCategoryGridTest.xml | 5 +++ ...ancedMediaGalleryVerifyAssetFilterTest.xml | 14 +++++---- 11 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml index 02635f8e379cf..7c3a0165c28d0 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml @@ -17,7 +17,8 @@ </arguments> <conditionalClick selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" dependentSelector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> - <fillField selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.search}}" userInput="{{categoryName}}" stepKey="fillKeywordSearchField"/> - <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.submitSearch}}" stepKey="clickKeywordSearch"/> + <fillField selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.searchInput}}" userInput="{{categoryName}}" stepKey="fillKeywordSearchField"/> + <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.submitSearch}}" stepKey="clickKeywordSearch"/> + <waitForLoadingMaskToDisappear stepKey="waitingForLoading" /> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml index b698fa05600d7..cec17bdbb1428 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml @@ -10,47 +10,20 @@ <actionGroup name="AssertAdminCategoryGridPageDetailsActionGroup"> <arguments> <argument name="category"/> - <argument name="imageName" type="string" defaultValue="magento"/> </arguments> <annotations> - <description>Assert category grid page basic columns values for a specific category</description> + <description>Assert category grid page name and path column values for a specific category</description> </annotations> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(imageName)}}" stepKey="assertImageColumn"/> - - <!--Name Column--> <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Name')}}" stepKey="grabNameColumnValue"/> <assertEquals stepKey="assertNameColumn"> <expectedResult type="string">$$category.name$$</expectedResult> <actualResult type="variable">grabNameColumnValue</actualResult> </assertEquals> - - <!--Path Column--> <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Path')}}" stepKey="grabPathColumnValue"/> <assertStringContainsString stepKey="assertPathColumn"> <expectedResult type="string">$$category.name$$</expectedResult> <actualResult type="variable">grabPathColumnValue</actualResult> </assertStringContainsString> - - <!--Products Column--> - <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Products')}}" stepKey="grabProductsColumnValue"/> - <assertEquals stepKey="assertProductsColumn"> - <expectedResult type="string">0</expectedResult> - <actualResult type="variable">grabProductsColumnValue</actualResult> - </assertEquals> - - <!--In Menu Column--> - <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('In Menu')}}" stepKey="grabInMenuColumnValue"/> - <assertEquals stepKey="assertInMenuColumn"> - <expectedResult type="string">Yes</expectedResult> - <actualResult type="variable">grabInMenuColumnValue</actualResult> - </assertEquals> - - <!--Enabled Column--> - <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Enabled')}}" stepKey="grabEnabledColumnValue"/> - <assertEquals stepKey="assertEnabledColumn"> - <expectedResult type="string">Yes</expectedResult> - <actualResult type="variable">grabEnabledColumnValue</actualResult> - </assertEquals> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml new file mode 100644 index 0000000000000..b110ce44a8469 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.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="AssertAdminCategoryGridPageImageColumnActionGroup"> + <arguments> + <argument name="file" type="string" defaultValue="magento"/> + </arguments> + <annotations> + <description>Assert category grid page image column a specific category</description> + </annotations> + + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(file)}}" stepKey="assertImageColumn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml index 66f5a74d17d94..72b1bca56cb6e 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml @@ -15,10 +15,10 @@ <description>Assert the number of records in the category grid page.</description> </annotations> - <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.recordsLabel}}" stepKey="grabRecordsLabel"/> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.numberOfRecordsFound}}" stepKey="grabNumberOfRecordsFound"/> <assertEquals stepKey="assertStringIsEqual"> <expectedResult type="string">{{numberOfRecords}}</expectedResult> - <actualResult type="variable">grabRecordsLabel</actualResult> + <actualResult type="variable">grabNumberOfRecordsFound</actualResult> </assertEquals> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml new file mode 100644 index 0000000000000..e5d6f26e777fc --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml @@ -0,0 +1,31 @@ +<?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="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup"> + <annotations> + <description>Assert category grid page products, in menu, and enabled column values for a specific category</description> + </annotations> + + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Products')}}" stepKey="grabProductsColumnValue"/> + <assertEquals stepKey="assertProductsColumn"> + <expectedResult type="string">0</expectedResult> + <actualResult type="variable">grabProductsColumnValue</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('In Menu')}}" stepKey="grabInMenuColumnValue"/> + <assertEquals stepKey="assertInMenuColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabInMenuColumnValue</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Enabled')}}" stepKey="grabEnabledColumnValue"/> + <assertEquals stepKey="assertEnabledColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabEnabledColumnValue</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml index 99cee48f443c7..59775dd148712 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml @@ -7,6 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminMediaGalleryCatalogUiCategoryGridPage" url="media_gallery_catalog/category/index" area="admin" module="Magento_MediaGalleryCatalogUi"> + <section name="AdminMediaGalleryCatalogUiCategoryGridSearchSection"/> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"/> </page> </pages> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml new file mode 100644 index 0000000000000..867721d1e42bb --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminMediaGalleryCatalogUiCategoryGridSearchSection"> + <element name="searchInput" type="input" selector=".admin__data-grid-header input[placeholder='Search by category name']"/> + <element name="submitSearch" type="button" selector=".data-grid-search-control-wrap > button.action-submit" timeout="30"/> + <element name="numberOfRecordsFound" type="text" selector=".admin__data-grid-header .admin__control-support-text"/> + </section> +</sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 1d5f4e7cad5ea..f65ec84bc2ec8 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,16 +9,9 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"> - <!--Search by category name element--> - <element name="search" type="input" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] input[placeholder='Search by category name']"/> - <element name="submitSearch" type="button" selector=".data-grid-search-control-wrap > button.action-submit" timeout="30"/> - <!--Records element--> - <element name="recordsLabel" type="text" selector=".admin__data-grid-header .admin__control-support-text"/> - <!--Filter element--> <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> <element name="activeFilterPlaceholder" type="text" selector="//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> - <!--Category Grid Page Columns--> - <element name="image" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Image')]/preceding-sibling::th) +1]//img[contains(@src, '{{imageName}}')]" parameterized="true"/> + <element name="image" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Image')]/preceding-sibling::th) +1]//img[contains(@src, '{{file}}')]" parameterized="true"/> <element name="columnValue" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{columnName}}')]/preceding-sibling::th) +1 ]//div" parameterized="true"/> <element name="edit" type="button" selector="//tr[td//text()[contains(., '{{categoryName}}')]]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{actionButton}}']" parameterized="true"/> </section> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml index 6ca2500723400..fde9597155d0c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml @@ -26,13 +26,17 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> - <actionGroup ref="AdminSearchCategoryGridPageByCategoryNameActionGroup" stepKey="searchByCategoryName"> <argument name="categoryName" value="$$category.name$$"/> </actionGroup> - + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"/> <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> <argument name="category" value="$$category$$"/> </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> </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 4877d0caf88f4..7d8ffc6b8fc12 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -60,8 +60,13 @@ <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> <argument name="numberOfRecords" value="1 records found"/> </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"> + <argument name="file" value="{{UpdatedImageDetails.file}}"/> + </actionGroup> <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> <argument name="category" value="$$category$$"/> </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml index 1c2a6d7ed1d01..9a08f7cd0bb9c 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml @@ -72,12 +72,14 @@ <argument name="optionName" value="{{ImageMetadata.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridApplyFiltersActionGroup" stepKey="applyFilters"/> - - <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Name')}}" stepKey="grabCategoryNameFromGrid"/> - <assertEquals stepKey="assertEqualCategoryName"> - <expectedResult type="string">$$category.name$$</expectedResult> - <actualResult type="variable">grabCategoryNameFromGrid</actualResult> - </assertEquals> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"/> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> </test> </tests> From af2908bd7e8660094d4af3adfa6ad59939daa459 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 27 Aug 2020 00:56:33 +0800 Subject: [PATCH 69/95] magento/adobe-stock-integration#1775: [MFTF] Make AdminAssertCategoryGridPageDetailsActionGroup parametrized - modified mftf test file --- ...alogUiVerifyUsedInLinkCategoryGridTest.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml index 93586daec0288..f4efa35dfc881 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -57,6 +57,16 @@ <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterApplied"> <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"> + <argument name="file" value="{{UpdatedImageDetails.file}}"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilters"/> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPageToVerifyIfFilterCanBeApplied"/> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridExpandFilterActionGroup" stepKey="expandFilters"/> @@ -68,16 +78,6 @@ <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterAppliedAfterUrlFilterApplier"> <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> </actionGroup> - <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> - <argument name="numberOfRecords" value="1 records found"/> - </actionGroup> - <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"> - <argument name="file" value="{{UpdatedImageDetails.file}}"/> - </actionGroup> - <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> - <argument name="category" value="$$category$$"/> - </actionGroup> - <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> </test> </tests> From 4a258ec1188dcc65787fd52da74428724c10c6af Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Wed, 26 Aug 2020 18:28:23 +0100 Subject: [PATCH 70/95] Intoroduced MediaGallerySynchronizationMetadata module --- .../Model/GetAssetFromPath.php | 1 + .../Model/SynchronizeFilesTest.php | 70 ++------ .../MediaGallerySynchronization/composer.json | 3 +- .../MediaGallerySynchronization/etc/di.xml | 3 +- .../Model/CreateAssetFromFile.php | 16 +- .../LICENSE.txt | 48 ++++++ .../LICENSE_AFL.txt | 48 ++++++ .../Model/ImportKeywords.php} | 14 +- .../Plugin/CreateAssetFromFileMetadata.php | 81 ++++++++++ .../README.md | 3 + .../composer.json | 24 +++ .../etc/di.xml | 19 +++ .../etc/module.xml | 10 ++ .../registration.php | 14 ++ composer.json | 1 + composer.lock | 82 ++-------- .../Model/SynchronizeFilesTest.php | 151 ++++++++++++++++++ .../_files/magento.jpg | Bin 0 -> 55303 bytes .../_files/magento_metadata.jpg | Bin 19 files changed, 432 insertions(+), 156 deletions(-) rename app/code/Magento/{MediaGallerySynchronization => MediaGallerySynchronizationApi}/Model/CreateAssetFromFile.php (84%) create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt rename app/code/Magento/{MediaGallerySynchronization/Model/ImportImageFileKeywords.php => MediaGallerySynchronizationMetadata/Model/ImportKeywords.php} (93%) create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/README.md create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/composer.json create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml create mode 100644 app/code/Magento/MediaGallerySynchronizationMetadata/registration.php create mode 100644 dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php create mode 100644 dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg rename {app/code/Magento/MediaGallerySynchronization/Test/Integration => dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata}/_files/magento_metadata.jpg (100%) diff --git a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php index 533d814c9f1d0..ef23e09dfa1fa 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php @@ -12,6 +12,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile; /** * Create media asset object based on the file information diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php index 6c4338c0935dc..8a44307298065 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php @@ -9,14 +9,12 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\DriverInterface; -use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use Magento\MediaGalleryApi\Api\Data\KeywordInterface; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; -use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -45,11 +43,6 @@ class SynchronizeFilesTest extends TestCase */ private $mediaDirectory; - /** - * @var GetAssetsKeywordsInterface - */ - private $getAssetKeywords; - /** * @inheritdoc */ @@ -58,7 +51,6 @@ protected function setUp(): void $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); - $this->getAssetKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); } @@ -67,18 +59,16 @@ protected function setUp(): void * Test for SynchronizeFiles::execute * * @dataProvider filesProvider - * @param null|string $file - * @param null|string $title - * @param null|string $description - * @param null|array $keywords + * @param string $file + * @param string $title + * @param string $source * @throws FileSystemException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function testExecute( - ?string $file, - ?string $title, - ?string $description, - ?array $keywords + string $file, + string $title, + string $source ): void { $path = realpath(__DIR__ . '/../_files/' . $file); $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); @@ -89,12 +79,10 @@ public function testExecute( $this->synchronizeFiles->execute([$file]); - $loadedAssets = $this->getAssetsByPath->execute([$file])[0]; - $loadedKeywords = $this->getKeywords($loadedAssets) ?: null; + $loadedAsset = $this->getAssetsByPath->execute([$file])[0]; - $this->assertEquals($title, $loadedAssets->getTitle()); - $this->assertEquals($description, $loadedAssets->getDescription()); - $this->assertEquals($keywords, $loadedKeywords); + $this->assertEquals($title, $loadedAsset->getTitle()); + $this->assertEquals($source, $loadedAsset->getSource()); $this->driver->deleteFile($modifiableFilePath); } @@ -110,42 +98,8 @@ public function filesProvider(): array [ '/magento.jpg', 'magento', - null, - null - ], - [ - '/magento_metadata.jpg', - 'Title of the magento image', - 'Description of the magento image', - [ - 'magento', - 'mediagallerymetadata' - ] + 'Local' ] ]; } - - /** - * Key asset keywords - * - * @param AssetInterface $asset - * @return string[] - */ - private function getKeywords(AssetInterface $asset): array - { - $assetKeywords = $this->getAssetKeywords->execute([$asset->getId()]); - - if (empty($assetKeywords)) { - return []; - } - - $keywords = current($assetKeywords)->getKeywords(); - - return array_map( - function (KeywordInterface $keyword) { - return $keyword->getKeyword(); - }, - $keywords - ); - } } diff --git a/app/code/Magento/MediaGallerySynchronization/composer.json b/app/code/Magento/MediaGallerySynchronization/composer.json index e1d4962366978..f9d642dd02568 100644 --- a/app/code/Magento/MediaGallerySynchronization/composer.json +++ b/app/code/Magento/MediaGallerySynchronization/composer.json @@ -6,8 +6,7 @@ "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-synchronization-api": "*", - "magento/framework-message-queue": "*", - "magento/module-media-gallery-metadata-api": "*" + "magento/framework-message-queue": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGallerySynchronization/etc/di.xml b/app/code/Magento/MediaGallerySynchronization/etc/di.xml index 47a4360575b2e..4b9ffcbe63c76 100644 --- a/app/code/Magento/MediaGallerySynchronization/etc/di.xml +++ b/app/code/Magento/MediaGallerySynchronization/etc/di.xml @@ -12,8 +12,7 @@ <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> <arguments> <argument name="importers" xsi:type="array"> - <item name="0" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportMediaAsset</item> - <item name="1" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportImageFileKeywords</item> + <item name="10" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportMediaAsset</item> </argument> </arguments> </type> diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php similarity index 84% rename from app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php rename to app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php index 80b334733ed43..0e11487ecfa73 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\MediaGallerySynchronization\Model; +namespace Magento\MediaGallerySynchronizationApi\Model; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; @@ -14,7 +14,6 @@ use Magento\Framework\Filesystem\Driver\File; use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; -use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; use Magento\MediaGallerySynchronization\Model\GetContentHash; @@ -43,11 +42,6 @@ class CreateAssetFromFile */ private $getContentHash; - /** - * @var ExtractMetadataInterface - */ - private $extractMetadata; - /** * @var GetFileInfo */ @@ -58,7 +52,6 @@ class CreateAssetFromFile * @param File $driver * @param AssetInterfaceFactory $assetFactory * @param GetContentHash $getContentHash - * @param ExtractMetadataInterface $extractMetadata * @param GetFileInfo $getFileInfo */ public function __construct( @@ -66,14 +59,12 @@ public function __construct( File $driver, AssetInterfaceFactory $assetFactory, GetContentHash $getContentHash, - ExtractMetadataInterface $extractMetadata, GetFileInfo $getFileInfo ) { $this->filesystem = $filesystem; $this->driver = $driver; $this->assetFactory = $assetFactory; $this->getContentHash = $getContentHash; - $this->extractMetadata = $extractMetadata; $this->getFileInfo = $getFileInfo; } @@ -90,14 +81,11 @@ public function execute(string $path): AssetInterface $file = $this->getFileInfo->execute($absolutePath); [$width, $height] = getimagesize($absolutePath); - $metadata = $this->extractMetadata->execute($absolutePath); - return $this->assetFactory->create( [ 'id' => null, 'path' => $path, - 'title' => $metadata->getTitle() ?: $file->getBasename(), - 'description' => $metadata->getDescription(), + 'title' => $file->getBasename(), 'width' => $width, 'height' => $height, 'hash' => $this->getHash($path), diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php b/app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php similarity index 93% rename from app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php rename to app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php index 361137ad27686..a9910157f27c7 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php @@ -5,12 +5,11 @@ */ declare(strict_types=1); -namespace Magento\MediaGallerySynchronization\Model; +namespace Magento\MediaGallerySynchronizationMetadata\Model; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; -use Magento\Framework\Filesystem\Driver\File; use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterfaceFactory; use Magento\MediaGalleryApi\Api\Data\KeywordInterface; use Magento\MediaGalleryApi\Api\Data\KeywordInterfaceFactory; @@ -22,18 +21,13 @@ /** * import image keywords from file metadata */ -class ImportImageFileKeywords implements ImportFilesInterface +class ImportKeywords implements ImportFilesInterface { /** * @var Filesystem */ private $filesystem; - /** - * @var File - */ - private $driver; - /** * @var KeywordInterfaceFactory */ @@ -60,7 +54,6 @@ class ImportImageFileKeywords implements ImportFilesInterface private $getAssetsByPaths; /** - * @param File $driver * @param Filesystem $filesystem * @param KeywordInterfaceFactory $keywordFactory * @param ExtractMetadataInterface $extractMetadata @@ -69,7 +62,6 @@ class ImportImageFileKeywords implements ImportFilesInterface * @param GetAssetsByPathsInterface $getAssetsByPaths */ public function __construct( - File $driver, Filesystem $filesystem, KeywordInterfaceFactory $keywordFactory, ExtractMetadataInterface $extractMetadata, @@ -77,7 +69,6 @@ public function __construct( AssetKeywordsInterfaceFactory $assetKeywordsFactory, GetAssetsByPathsInterface $getAssetsByPaths ) { - $this->driver = $driver; $this->filesystem = $filesystem; $this->keywordFactory = $keywordFactory; $this->extractMetadata = $extractMetadata; @@ -123,6 +114,7 @@ private function getMetadataKeywords(string $path): ?array { $metadataKeywords = $this->extractMetadata->execute($this->getMediaDirectory()->getAbsolutePath($path)) ->getKeywords(); + if ($metadataKeywords === null) { return null; } diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php new file mode 100644 index 0000000000000..1f67a871b57ae --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationMetadata\Plugin; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile; + +/** + * Add metadata to the asset created from file + */ +class CreateAssetFromFileMetadata +{ + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var AssetInterfaceFactory + */ + private $assetFactory; + + /** + * @var ExtractMetadataInterface + */ + private $extractMetadata; + + /** + * @param Filesystem $filesystem + * @param AssetInterfaceFactory $assetFactory + * @param ExtractMetadataInterface $extractMetadata + */ + public function __construct( + Filesystem $filesystem, + AssetInterfaceFactory $assetFactory, + ExtractMetadataInterface $extractMetadata + ) { + $this->filesystem = $filesystem; + $this->assetFactory = $assetFactory; + $this->extractMetadata = $extractMetadata; + } + + /** + * Add metadata to the asset + * + * @param CreateAssetFromFile $createAssetFromFile + * @param AssetInterface $asset + * @return AssetInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute(CreateAssetFromFile $createAssetFromFile, AssetInterface $asset): AssetInterface + { + $metadata = $this->extractMetadata->execute( + $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath($asset->getPath()) + ); + + return $this->assetFactory->create( + [ + 'id' => $asset->getId(), + 'path' => $asset->getPath(), + 'title' => $metadata->getTitle() ?: $asset->getTitle(), + 'description' => $metadata->getDescription(), + 'width' => $asset->getWidth(), + 'height' => $asset->getHeight(), + 'hash' => $asset->getHash(), + 'size' => $asset->getSize(), + 'contentType' => $asset->getContentType(), + 'source' => $asset->getSource() + ] + ); + } +} diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/README.md b/app/code/Magento/MediaGallerySynchronizationMetadata/README.md new file mode 100644 index 0000000000000..64988dd543fe4 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/README.md @@ -0,0 +1,3 @@ +# Magento_MediaGallerySynchronizationMetadata + +The purpose of this module is to include assets metadata to media gallery synchronization process diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json new file mode 100644 index 0000000000000..0674014026b24 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-media-gallery-synchronization-metadata", + "description": "Magento module responsible for images metadata synchronization", + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-media-gallery-api": "*", + "magento/module-media-gallery-metadata-api": "*", + "magento/module-media-gallery-synchronization-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaGallerySynchronizationMetadata\\": "" + } + } +} diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml new file mode 100644 index 0000000000000..c82350f617b5b --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> + <arguments> + <argument name="importers" xsi:type="array"> + <item name="20" xsi:type="object">Magento\MediaGallerySynchronizationMetadata\Model\ImportKeywords</item> + </argument> + </arguments> + </type> + <type name="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile"> + <plugin name="addMetadataToAssetCreatedFromFile" type="Magento\MediaGallerySynchronizationMetadata\Plugin\CreateAssetFromFileMetadata"/> + </type> +</config> diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml new file mode 100644 index 0000000000000..f92c370496d2d --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_MediaGallerySynchronizationMetadata"/> +</config> diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php b/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php new file mode 100644 index 0000000000000..82315db519f82 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register( + ComponentRegistrar::MODULE, + 'Magento_MediaGallerySynchronizationMetadata', + __DIR__ +); diff --git a/composer.json b/composer.json index 25be12b5bb72f..1af86e438882c 100644 --- a/composer.json +++ b/composer.json @@ -215,6 +215,7 @@ "magento/module-media-content-synchronization-api": "*", "magento/module-media-content-synchronization-catalog": "*", "magento/module-media-content-synchronization-cms": "*", + "magento/module-media-gallery-synchronization-metadata": "*", "magento/module-media-gallery-metadata": "*", "magento/module-media-gallery-metadata-api": "*", "magento/module-media-gallery-catalog-ui": "*", diff --git a/composer.lock b/composer.lock index c2eed9d87cc00..9f079570cc2ac 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0b51badfd1978bb34febd90226af9e27", + "content-hash": "aadcf8a265dd7ecbb86dd3dd4e49bc28", "packages": [ { "name": "colinmollenhour/cache-backend-file", @@ -206,6 +206,16 @@ "ssl", "tls" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], "time": "2020-04-08T08:27:21+00:00" }, { @@ -1346,12 +1356,6 @@ "BSD-3-Clause" ], "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T13:45:39+00:00" }, { @@ -3319,12 +3323,6 @@ "laminas", "zf" ], - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T16:45:56+00:00" }, { @@ -3564,16 +3562,6 @@ "logging", "psr-3" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], "time": "2020-05-22T07:31:27+00:00" }, { @@ -4366,16 +4354,6 @@ "parser", "validator" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], "time": "2020-04-30T19:05:18+00:00" }, { @@ -7593,12 +7571,6 @@ "sftp", "storage" ], - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } - ], "time": "2020-05-18T15:13:39+00:00" }, { @@ -8817,20 +8789,6 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], "time": "2020-05-05T12:55:44+00:00" }, { @@ -9120,12 +9078,6 @@ "keywords": [ "timer" ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-04-20T06:00:37+00:00" }, { @@ -9181,6 +9133,7 @@ "type": "github" } ], + "abandoned": true, "time": "2020-06-27T06:36:25+00:00" }, { @@ -9269,16 +9222,6 @@ "testing", "xunit" ], - "funding": [ - { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-05-22T13:54:05+00:00" }, { @@ -9786,6 +9729,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", + "abandoned": true, "time": "2020-02-08T06:07:58+00:00" }, { diff --git a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php new file mode 100644 index 0000000000000..52e7191a97226 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationMetadata\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\Data\KeywordInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; +use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for SynchronizeFiles. + */ +class SynchronizeFilesTest extends TestCase +{ + /** + * @var DriverInterface + */ + private $driver; + + /** + * @var SynchronizeFilesInterface + */ + private $synchronizeFiles; + + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @var WriteInterface + */ + private $mediaDirectory; + + /** + * @var GetAssetsKeywordsInterface + */ + private $getAssetKeywords; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + $this->getAssetKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); + $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); + } + + /** + * Test for SynchronizeFiles::execute + * + * @dataProvider filesProvider + * @param null|string $file + * @param null|string $title + * @param null|string $description + * @param null|array $keywords + * @throws FileSystemException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testExecute( + ?string $file, + ?string $title, + ?string $description, + ?array $keywords + ): void { + $path = realpath(__DIR__ . '/../_files/' . $file); + $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); + $this->driver->copy( + $path, + $modifiableFilePath + ); + + $this->synchronizeFiles->execute([$file]); + + $loadedAssets = $this->getAssetsByPath->execute([$file])[0]; + $loadedKeywords = $this->getKeywords($loadedAssets) ?: null; + + $this->assertEquals($title, $loadedAssets->getTitle()); + $this->assertEquals($description, $loadedAssets->getDescription()); + $this->assertEquals($keywords, $loadedKeywords); + + $this->driver->deleteFile($modifiableFilePath); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function filesProvider(): array + { + return [ + [ + '/magento.jpg', + 'magento', + null, + null + ], + [ + '/magento_metadata.jpg', + 'Title of the magento image', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ] + ]; + } + + /** + * Key asset keywords + * + * @param AssetInterface $asset + * @return string[] + */ + private function getKeywords(AssetInterface $asset): array + { + $assetKeywords = $this->getAssetKeywords->execute([$asset->getId()]); + + if (empty($assetKeywords)) { + return []; + } + + $keywords = current($assetKeywords)->getKeywords(); + + return array_map( + function (KeywordInterface $keyword) { + return $keyword->getKeyword(); + }, + $keywords + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c377daf8fb0b390d89aa4f6f111715fc9118ca1b GIT binary patch literal 55303 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 Hh5vs5{BvNq literal 0 HcmV?d00001 diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_metadata.jpg b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento_metadata.jpg similarity index 100% rename from app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_metadata.jpg rename to dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento_metadata.jpg From d7ab12a1f70fa95338cb6e7e604b9e45a6a19cf1 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Wed, 26 Aug 2020 14:51:12 -0500 Subject: [PATCH 71/95] MQE-2271: Release 3.1.0 Delivery Composer update --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1f91e7b8594a1..25be12b5bb72f 100644 --- a/composer.json +++ b/composer.json @@ -88,7 +88,7 @@ "friendsofphp/php-cs-fixer": "~2.16.0", "lusitanian/oauth": "~0.8.10", "magento/magento-coding-standard": "*", - "magento/magento2-functional-testing-framework": "^3.1", + "magento/magento2-functional-testing-framework": "^3.0", "pdepend/pdepend": "~2.7.1", "phpcompatibility/php-compatibility": "^9.3", "phpmd/phpmd": "^2.8.0", diff --git a/composer.lock b/composer.lock index 5b7e6c3da431a..36a42d0c750df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b5562151b3be7e921e3ebc8080da557f", + "content-hash": "197f0388c574f9d40555a95634e439e0", "packages": [ { "name": "colinmollenhour/cache-backend-file", From 8e7c183f788a152de38f9cdb6f8971bc67c73ad6 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Wed, 26 Aug 2020 21:19:49 +0100 Subject: [PATCH 72/95] magento/magento2#29761: Added CreateAssetFromFileInterface --- .../Model/CreateAssetFromFile.php | 12 +++------ .../Model/GetAssetFromPath.php | 8 +++--- .../MediaGallerySynchronization/etc/di.xml | 1 + .../Model/CreateAssetFromFileInterface.php | 26 +++++++++++++++++++ .../composer.json | 3 ++- .../Plugin/CreateAssetFromFileMetadata.php | 6 ++--- .../etc/di.xml | 2 +- 7 files changed, 41 insertions(+), 17 deletions(-) rename app/code/Magento/{MediaGallerySynchronizationApi => MediaGallerySynchronization}/Model/CreateAssetFromFile.php (91%) create mode 100644 app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php diff --git a/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php similarity index 91% rename from app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php rename to app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 0e11487ecfa73..5bfc2fd54ed2a 100644 --- a/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\MediaGallerySynchronizationApi\Model; +namespace Magento\MediaGallerySynchronization\Model; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; @@ -15,12 +15,12 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; -use Magento\MediaGallerySynchronization\Model\GetContentHash; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; /** * Create media asset object based on the file information */ -class CreateAssetFromFile +class CreateAssetFromFile implements CreateAssetFromFileInterface { /** * @var Filesystem @@ -69,11 +69,7 @@ public function __construct( } /** - * Create and format media asset object - * - * @param string $path - * @return AssetInterface - * @throws FileSystemException + * @inheridoc */ public function execute(string $path): AssetInterface { diff --git a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php index ef23e09dfa1fa..be672666786dd 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php @@ -12,7 +12,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; -use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; /** * Create media asset object based on the file information @@ -30,19 +30,19 @@ class GetAssetFromPath private $assetFactory; /** - * @var CreateAssetFromFile + * @var CreateAssetFromFileInterface */ private $createAssetFromFile; /** * @param AssetInterfaceFactory $assetFactory * @param GetAssetsByPathsInterface $getMediaGalleryAssetByPath - * @param CreateAssetFromFile $createAssetFromFile + * @param CreateAssetFromFileInterface $createAssetFromFile */ public function __construct( AssetInterfaceFactory $assetFactory, GetAssetsByPathsInterface $getMediaGalleryAssetByPath, - CreateAssetFromFile $createAssetFromFile + CreateAssetFromFileInterface $createAssetFromFile ) { $this->assetFactory = $assetFactory; $this->getAssetsByPaths = $getMediaGalleryAssetByPath; diff --git a/app/code/Magento/MediaGallerySynchronization/etc/di.xml b/app/code/Magento/MediaGallerySynchronization/etc/di.xml index 4b9ffcbe63c76..82bd1303eda74 100644 --- a/app/code/Magento/MediaGallerySynchronization/etc/di.xml +++ b/app/code/Magento/MediaGallerySynchronization/etc/di.xml @@ -9,6 +9,7 @@ <preference for="Magento\MediaGallerySynchronizationApi\Api\SynchronizeInterface" type="Magento\MediaGallerySynchronization\Model\Synchronize"/> <preference for="Magento\MediaGallerySynchronizationApi\Model\FetchBatchesInterface" type="Magento\MediaGallerySynchronization\Model\FetchBatches"/> <preference for="Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface" type="Magento\MediaGallerySynchronization\Model\SynchronizeFiles"/> + <preference for="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface" type="Magento\MediaGallerySynchronization\Model\CreateAssetFromFile"/> <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> <arguments> <argument name="importers" xsi:type="array"> diff --git a/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php new file mode 100644 index 0000000000000..667c2e68a27d8 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationApi\Model; + +use Magento\Framework\Exception\FileSystemException; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; + +/** + * Create media asset object from the media file + */ +interface CreateAssetFromFileInterface +{ + /** + * Create media asset object from the media file + * + * @param string $path + * @return AssetInterface + * @throws FileSystemException + */ + public function execute(string $path): AssetInterface; +} diff --git a/app/code/Magento/MediaGallerySynchronizationApi/composer.json b/app/code/Magento/MediaGallerySynchronizationApi/composer.json index 427bd2bd4aca7..19bab75dd5f42 100644 --- a/app/code/Magento/MediaGallerySynchronizationApi/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationApi/composer.json @@ -3,7 +3,8 @@ "description": "Magento module responsible for the media gallery synchronization implementation API", "require": { "php": "~7.3.0||~7.4.0", - "magento/framework": "*" + "magento/framework": "*", + "magento/module-media-gallery-api": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php index 1f67a871b57ae..59604c0b3e501 100644 --- a/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php @@ -12,7 +12,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; -use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; /** * Add metadata to the asset created from file @@ -52,12 +52,12 @@ public function __construct( /** * Add metadata to the asset * - * @param CreateAssetFromFile $createAssetFromFile + * @param CreateAssetFromFileInterface $subject * @param AssetInterface $asset * @return AssetInterface * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterExecute(CreateAssetFromFile $createAssetFromFile, AssetInterface $asset): AssetInterface + public function afterExecute(CreateAssetFromFileInterface $subject, AssetInterface $asset): AssetInterface { $metadata = $this->extractMetadata->execute( $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath($asset->getPath()) diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml index c82350f617b5b..ed66fd08cabfc 100644 --- a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml @@ -13,7 +13,7 @@ </argument> </arguments> </type> - <type name="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFile"> + <type name="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface"> <plugin name="addMetadataToAssetCreatedFromFile" type="Magento\MediaGallerySynchronizationMetadata\Plugin\CreateAssetFromFileMetadata"/> </type> </config> From d178bc1db4c7360214212e4326f4bd16442a4f57 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 27 Aug 2020 05:11:51 +0800 Subject: [PATCH 73/95] magento/adobe-stock-integration#1776: [MFTF] Sorting in Media Gallery - introduced new ActionGroup and Sections for media gallery sorting test --- ...hancedMediaGalleryClickSortActionGroup.xml | 19 +++++++++++++++++++ ...edMediaGalleryGridImagePositionSection.xml | 13 +++++++++++++ ...AdminEnhancedMediaGallerySortBySection.xml | 14 ++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml new file mode 100644 index 0000000000000..7679da6585d5f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml @@ -0,0 +1,19 @@ +<?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="AdminEnhancedMediaGalleryClickSortActionGroup"> + <arguments> + <argument name="sortName" type="string"/> + </arguments> + <click selector="{{AdminEnhancedMediaGallerySortBySection.sortDropdown}}" stepKey="clickOnSortDropdown"/> + <click selector="{{AdminEnhancedMediaGallerySortBySection.sortOption(sortName)}}" stepKey="clickOnSortOption"/> + <waitForPageLoad stepKey="waitForLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml new file mode 100644 index 0000000000000..943f29d5fa851 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEnhancedMediaGalleryGridImagePositionSection"> + <element name="nthImageInGrid" type="text" selector="div[class='masonry-image-column'][data-repeat-index='{{row}}'] img" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml new file mode 100644 index 0000000000000..5ffcec00fcf4b --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEnhancedMediaGallerySortBySection"> + <element name="sortDropdown" type="button" selector="div[class='masonry-image-sortby'] select"/> + <element name="sortOption" type="button" selector="//div[@class='masonry-image-sortby'] //option[@value='{{sortOption}}']" parameterized="true"/> + </section> +</sections> From 5d523ad8554f1bcbc13860ba5e18d65f1f7ed8cc Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Wed, 26 Aug 2020 18:02:40 -0500 Subject: [PATCH 74/95] MC-37041: MFTF StorefrontAddStoreCodeInUrl fails when using Varnish --- .../Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml index 74264149cf1cb..9731b66209df0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml @@ -34,6 +34,9 @@ <deleteData createDataKey="category3" stepKey="deleteCategory3"/> <deleteData createDataKey="category2" stepKey="deleteCategory2"/> <deleteData createDataKey="category1" stepKey="deleteCategory1"/> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="full_page"/> + </actionGroup> </after> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="amOnStorefrontPage"/> <moveMouseOver From a80fa10f665e1da1d64bed6ea7187bef2aa00d2b Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin <dyushkin@adobe.com> Date: Wed, 26 Aug 2020 19:08:48 -0500 Subject: [PATCH 75/95] MC-35161: No Payment method is showing in Admin Create Order for one website only. --- app/code/Magento/Sales/Model/AdminOrder/Create.php | 10 ++++++---- .../Magento/Sales/Model/AdminOrder/CreateTest.php | 8 ++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index d5a94a4dd1fcf..f60944d57c992 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -745,10 +745,12 @@ public function getCustomerCart() try { $this->_cart = $this->quoteRepository->getForCustomer($customerId, [$storeId]); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - $this->_cart->setStore($this->getSession()->getStore()); - $customerData = $this->customerRepository->getById($customerId); - $this->_cart->assignCustomer($customerData); - $this->quoteRepository->save($this->_cart); + if ($this->getQuote()->hasItems()) { + $this->_cart->setStore($this->getSession()->getStore()); + $customerData = $this->customerRepository->getById($customerId); + $this->_cart->assignCustomer($customerData); + $this->quoteRepository->save($this->_cart); + } } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php index e1cc942d4ae28..86e42e228d623 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php @@ -693,12 +693,8 @@ public function testGetCustomerCartNewCart() /** SUT execution */ $customerQuote = $this->model->getCustomerCart(); - self::assertNotEmpty($customerQuote->getId(), 'Quote ID is invalid.'); - self::assertEquals( - $customerEmailFromFixture, - $customerQuote->getCustomerEmail(), - 'Customer data is preserved incorrectly in a newly quote.' - ); + self::assertInstanceOf(Quote::class, $customerQuote); + self::assertEmpty($customerQuote->getData()); } /** From 605d211f3247317a148575d462f36d8dabd07360 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin <dyushkin@adobe.com> Date: Wed, 26 Aug 2020 21:24:12 -0500 Subject: [PATCH 76/95] MC-35161: No Payment method is showing in Admin Create Order for one website only. - Static tests fix --- app/code/Magento/Sales/Model/AdminOrder/Create.php | 6 ++++-- .../testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index f60944d57c992..8ef12e5889520 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -787,6 +787,7 @@ public function getCustomerCompareList() public function getCustomerGroupId() { $groupId = $this->getQuote()->getCustomerGroupId(); + // @phpstan-ignore-next-line if (!isset($groupId)) { $groupId = $this->getSession()->getCustomerGroupId(); } @@ -1445,9 +1446,10 @@ public function setShippingAddress($address) */ $saveInAddressBook = (int)(!empty($address['save_in_address_book'])); $shippingAddress->setData('save_in_address_book', $saveInAddressBook); - } - if ($address instanceof \Magento\Quote\Model\Quote\Address) { + } elseif ($address instanceof \Magento\Quote\Model\Quote\Address) { $shippingAddress = $address; + } else { + $shippingAddress = null; } $this->setRecollect(true); diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php index 86e42e228d623..3e6b27a7ca622 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php @@ -684,7 +684,6 @@ public function testMoveQuoteItemToCart() public function testGetCustomerCartNewCart() { $customerIdFromFixture = 1; - $customerEmailFromFixture = 'customer@example.com'; /** Preconditions */ /** @var SessionQuote $session */ From 2855c4d84cb75c40cc4749911eb372c6f478520d Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Thu, 27 Aug 2020 19:11:55 +0800 Subject: [PATCH 77/95] magento/adobe-stock-integration#1776: [MFTF] Sorting in media gallery - implement newest sort test --- .../Test/AdminMediaGallerySortingTest.xml | 80 ++++++++++++++++++- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml index a66d3d2af9182..395cf6140345d 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml @@ -21,11 +21,83 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> + + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> - <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openNewFolderForm"/> - <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createNewFolder"> - <argument name="name" value="a"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" + stepKey="getFirstImageBeforeNewestFirstSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" + stepKey="getSecondImageBeforeNewestFirstSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" + stepKey="getThirdImageBeforeNewestFirstSort"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNewestFirst"> + <argument name="sortName" value="newest_first"/> </actionGroup> - <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertNewFolderCreated"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" + stepKey="getFirstImageAfterNewestFirstSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" + stepKey="getSecondImageAfterNewestFirstSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" + stepKey="getThirdImageAfterNewestFirstSort"/> + <assertEquals stepKey="assertFirstImagePositionAfterNewestFirstSort"> + <actualResult type="string">{$getFirstImageAfterNewestFirstSort}</actualResult> + <expectedResult type="string">{$getFirstImageBeforeNewestFirstSort}</expectedResult> + </assertEquals> + <assertEquals stepKey="assertSecondImagePositionAfterNewestFirstSort"> + <actualResult type="string">{$getSecondImageAfterNewestFirstSort}</actualResult> + <expectedResult type="string">{$getSecondImageBeforeNewestFirstSort}</expectedResult> + </assertEquals> + <assertEquals stepKey="assertThirdImagePositionAfterNewestFirstSort"> + <actualResult type="string">{$getThirdImageAfterNewestFirstSort}</actualResult> + <expectedResult type="string">{$getThirdImageBeforeNewestFirstSort}</expectedResult> + </assertEquals> </test> </tests> From 266d80825d13ad010be3875bb75075a32f0817db Mon Sep 17 00:00:00 2001 From: janmonteros <janraymonteros@gmail.com> Date: Thu, 27 Aug 2020 19:56:06 +0800 Subject: [PATCH 78/95] magento/adobe-stock-integration#1783: Change the position of the main action buttons on View Details - change button order --- .../view/adminhtml/templates/image_details.phtml | 12 ++++++------ .../templates/image_details_standalone.phtml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml index ba2033478afa1..a6da20a255192 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml @@ -74,12 +74,6 @@ use Magento\Framework\Escaper; "imageModelName" : "media_gallery_listing.media_gallery_listing.media_gallery_columns.thumbnail_url", "mediaGalleryImageDetailsName": "mediaGalleryImageDetails", "actionsList": [ - { - "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", - "handler": "editImageAction", - "name": "edit", - "classes": "action-default scalable edit action-quaternary" - }, { "title": "<?= $escaper->escapeJs(__('Cancel')); ?>", "handler": "closeModal", @@ -92,6 +86,12 @@ use Magento\Framework\Escaper; "name": "delete", "classes": "action-default scalable delete action-quaternary" }, + { + "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", + "handler": "editImageAction", + "name": "edit", + "classes": "action-default scalable edit action-quaternary" + }, { "title": "<?= $escaper->escapeJs(__('Add Image')); ?>", "handler": "addImage", diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml index 9fc0e749ac888..288a2eaee5221 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml @@ -72,12 +72,6 @@ use Magento\Backend\Block\Template; "mediaGalleryImageDetailsName": "mediaGalleryImageDetails", "imageModelName" : "standalone_media_gallery_listing.standalone_media_gallery_listing.media_gallery_columns.thumbnail_url", "actionsList": [ - { - "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", - "handler": "editImageAction", - "name": "edit", - "classes": "action-default scalable edit action-quaternary" - }, { "title": "<?= $escaper->escapeJs(__('Cancel')); ?>", "handler": "closeModal", @@ -89,6 +83,12 @@ use Magento\Backend\Block\Template; "handler": "deleteImageAction", "name": "delete", "classes": "action-default scalable delete action-quaternary" + }, + { + "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", + "handler": "editImageAction", + "name": "edit", + "classes": "action-default scalable edit action-quaternary" } ] } From 7fe1b9d704036a47095538a9e41432f376cc79fa Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Aug 2020 19:07:10 +0300 Subject: [PATCH 79/95] Fix filter placeholders for ui-select filter (frontend implementation) --- .../Adminhtml/Product/GetSelected.php | 82 +++++++------ .../web/js/components/product-ui-select.js | 3 + .../Listing/Filters/UsedInProducts.php | 115 ------------------ .../media_gallery_category_listing.xml | 3 +- .../ui_component/media_gallery_listing.xml | 3 +- .../standalone_media_gallery_listing.xml | 3 +- .../Adminhtml/Block/GetSelected.php | 83 +++++++++++++ .../Controller/Adminhtml/Page/GetSelected.php | 83 +++++++++++++ .../Listing/Filters/UsedInBlocks.php | 115 ------------------ .../Component/Listing/Filters/UsedInPages.php | 114 ----------------- .../ui_component/media_gallery_listing.xml | 6 +- .../standalone_media_gallery_listing.xml | 8 +- .../Adminhtml/Asset/GetSelected.php | 99 +++++++++++++++ .../Ui/Component/Listing/Filters/Asset.php | 112 +---------------- .../ui_component/cms_block_listing.xml | 3 +- .../ui_component/cms_page_listing.xml | 3 +- .../ui_component/product_listing.xml | 3 +- 17 files changed, 333 insertions(+), 505 deletions(-) delete mode 100644 app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php create mode 100644 app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php create mode 100644 app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php delete mode 100644 app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php delete mode 100644 app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php create mode 100644 app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php index 841715180a229..c6e39b048c40b 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php @@ -8,67 +8,77 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; -use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Backend\App\Action\Context; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Backend\App\Action; -/** Returns information about selected product by product id. Returns empty array if product don't exist */ -class GetSelected extends \Magento\Backend\App\Action +/** + * Returns selected product by product id. for ui-select filter + */ +class GetSelected extends Action implements HttpGetActionInterface { /** - * Authorization level of a basic admin session - * * @see _isAllowed() */ const ADMIN_RESOURCE = 'Magento_Catalog::products'; /** - * @var \Magento\Framework\Controller\Result\JsonFactory + * @var JsonFactory */ private $resultJsonFactory; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + * @var ProductRepositoryInterface */ - private $productCollectionFactory; + private $productRepository; /** - * Search constructor. - * @param \Magento\Framework\Controller\Result\JsonFactory $jsonFactory - * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory - * @param \Magento\Backend\App\Action\Context $context + * GetSelected constructor. + * + * @param JsonFactory $jsonFactory + * @param ProductRepositoryInterface $productRepository + * @param Context $context */ public function __construct( - \Magento\Framework\Controller\Result\JsonFactory $jsonFactory, - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, - \Magento\Backend\App\Action\Context $context + JsonFactory $jsonFactory, + ProductRepositoryInterface $productRepository, + Context $context ) { $this->resultJsonFactory = $jsonFactory; - $this->productCollectionFactory = $productCollectionFactory; + $this->productRepository = $productRepository; parent::__construct($context); } /** - * @return \Magento\Framework\Controller\ResultInterface + * + * @return ResultInterface */ - public function execute() : \Magento\Framework\Controller\ResultInterface + public function execute() : ResultInterface { - $productId = $this->getRequest()->getParam('productId'); - /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ - $productCollection = $this->productCollectionFactory->create(); - $productCollection->addAttributeToSelect(ProductInterface::NAME); - $productCollection->addIdFilter($productId); - $option = []; - /** @var ProductInterface $product */ - if (!empty($productCollection->getFirstItem()->getData())) { - $product = $productCollection->getFirstItem(); - $option = [ - 'value' => $productId, - 'label' => $product->getName(), - 'is_active' => $product->getStatus(), - 'path' => $product->getSku(), - ]; + $productIds = $this->getRequest()->getParam('ids'); + $options = []; + + + if (!is_array($productIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); } - /** @var \Magento\Framework\Controller\Result\Json $resultJson */ - $resultJson = $this->resultJsonFactory->create(); - return $resultJson->setData($option); + foreach ($productIds as $id) { + try { + $product = $this->productRepository->getById($id); + $options[] = [ + 'value' => $product->getId(), + 'label' => $product->getName(), + 'is_active' => $product->getSatus(), + 'path' => $product->getSku() + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); } } diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js index fb7ea7a5bcd69..c04daf07db3dd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js @@ -3,6 +3,9 @@ * See COPYING.txt for license details. */ +/** + * @deprecated see Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js + */ define([ 'Magento_Ui/js/form/element/ui-select', 'jquery', diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php deleted file mode 100644 index d86617e12b8f8..0000000000000 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Catalog\Api\ProductRepositoryInterface; - -/** - * Used in products filter - */ -class UsedInProducts extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param ProductRepositoryInterface $productRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - ProductRepositoryInterface $productRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->productRepository = $productRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $productIds = []; - $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( - 'current', - $this->context->getNameSpace() - ); - if ($bookmark === null) { - parent::prepare(); - return; - } - - $applied = $bookmark->getConfig()['current']['filters']['applied']; - - if (isset($applied[$this->getName()])) { - $productIds = $applied[$this->getName()]; - } - - foreach ($productIds as $id) { - try { - $product = $this->productRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $product->getName(), - 'is_active' => $product->getStatus(), - 'path' => $product->getSku(), - 'optgroup' => false - ]; - } catch (\Exception $e) { - continue; - } - } - $this->optionsProvider = $options; - parent::prepare(); - } -} diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml index e12d90b95303b..17fe33e5b2bf5 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 @@ -58,7 +58,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -74,6 +74,7 @@ <item name="filterRateLimitMethod" xsi:type="string" translate="true">notifyWhenChangesStop</item> <item name="searchOptions" xsi:type="boolean">true</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml index 2ca58b6020fa7..a5491e3450451 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -13,8 +13,7 @@ name="product_id" provider="${ $.parentName }" sortOrder="110" - class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters\UsedInProducts" - component="Magento_Catalog/js/components/product-ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index 2ca58b6020fa7..a5491e3450451 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -13,8 +13,7 @@ name="product_id" provider="${ $.parentName }" sortOrder="110" - class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters\UsedInProducts" - component="Magento_Catalog/js/components/product-ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php new file mode 100644 index 0000000000000..a8beeb791f74a --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryCmsUi\Controller\Adminhtml\Block; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Cms\Api\BlockRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; + +/** + * Controller to get selected block for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::block'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var BlockRepositoryInterface + */ + private $blockRepository; + + /** + * @param JsonFactory $resultFactory + * @param BlockRepositoryInterface $blockRepository + * @param Context $context + */ + public function __construct( + JsonFactory $resultFactory, + BlockRepositoryInterface $blockRepository, + Context $context + ) { + $this->resultJsonFactory = $resultFactory; + $this->blockRepository = $blockRepository; + parent::__construct($context); + } + + /** + * Execute pages search. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $blockIds = $this->getRequest()->getParam('ids'); + + if (!is_array($blockIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($blockIds as $id) { + try { + $block = $this->blockRepository->getById($id); + $options[] = [ + 'value' => $block->getId(), + 'label' => $block->getTitle(), + 'is_active' => $block->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php new file mode 100644 index 0000000000000..33f87b93c01e1 --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryCmsUi\Controller\Adminhtml\Page; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; + +/** + * Controller to get selected page for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::page'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var PageRepositoryInterface + */ + private $pageRepository; + + /** + * @param JsonFactory $resultFactory + * @param PageRepositoryInterface $pageRepository + * @param Context $context + */ + public function __construct( + JsonFactory $resultFactory, + PageRepositoryInterface $pageRepository, + Context $context + ) { + $this->resultJsonFactory = $resultFactory; + $this->pageRepository = $pageRepository; + parent::__construct($context); + } + + /** + * Execute pages search. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $pageIds = $this->getRequest()->getParam('ids'); + + if (!is_array($pageIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($pageIds as $id) { + try { + $page = $this->pageRepository->getById($id); + $options[] = [ + 'value' => $page->getId(), + 'label' => $page->getTitle(), + 'is_active' => $page->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php deleted file mode 100644 index 66f8caa71d70a..0000000000000 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Cms\Api\BlockRepositoryInterface; - -/** - * Used in blocks filter - */ -class UsedInBlocks extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var BlockRepositoryInterface - */ - private $blockRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param BlockRepositoryInterface $blockRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - BlockRepositoryInterface $blockRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->blockRepository = $blockRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $blockIds = []; - $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( - 'current', - $this->context->getNameSpace() - ); - if ($bookmark === null) { - parent::prepare(); - return; - } - - $applied = $bookmark->getConfig()['current']['filters']['applied']; - - if (isset($applied[$this->getName()])) { - $blockIds = $applied[$this->getName()]; - } - - 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 deleted file mode 100644 index 78ab1b63d32d1..0000000000000 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Cms\Api\PageRepositoryInterface; - -/** - * Used in pages filter - */ -class UsedInPages extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var PageRepositoryInterface - */ - private $pageRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param PageRepositoryInterface $pageRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - PageRepositoryInterface $pageRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->pageRepository = $pageRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $pageIds = []; - $bookmark = $this->bookmarkManagement->getByIdentifierNamespace( - 'current', - $this->context->getNameSpace() - ); - if ($bookmark === null) { - parent::prepare(); - return; - } - - $applied = $bookmark->getConfig()['current']['filters']['applied']; - - 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/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml index 506a6cad5b68e..402866852711d 100644 --- a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -14,7 +14,7 @@ provider="${ $.parentName }" sortOrder="120" class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInPages" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -27,6 +27,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find pages</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/page/getSelected"/> </item> </argument> <settings> @@ -39,7 +40,7 @@ provider="${ $.parentName }" sortOrder="130" class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInBlocks" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -52,6 +53,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find blocks</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/block/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index 506a6cad5b68e..e49ba7a98c8ce 100644 --- a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -13,8 +13,7 @@ name="page_id" provider="${ $.parentName }" sortOrder="120" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInPages" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -27,6 +26,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find pages</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/page/getSelected"/> </item> </argument> <settings> @@ -38,8 +38,7 @@ name="block_id" provider="${ $.parentName }" sortOrder="130" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInBlocks" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -52,6 +51,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find blocks</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/block/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php new file mode 100644 index 0000000000000..725c67185ce77 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryUi\Controller\Adminhtml\Asset; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\Cms\Helper\Wysiwyg\Images; +use Magento\Cms\Model\Wysiwyg\Images\Storage; + +/** + * Controller to get selected asset for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::media_gallery'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var GetAssetsByIdsInterface + */ + private $getAssetById; + + /** + * @var Images + */ + private $images; + + /** + * @var Storage + */ + private $storage; + + /** + * @param JsonFactory $resultFactory + * @param GetAssetsByIdsInterface $getAssetById + * @param Context $context + * @param Images $images + * @param Storage $storage + * + */ + public function __construct( + JsonFactory $resultFactory, + GetAssetsByIdsInterface $getAssetById, + Context $context, + Images $images, + Storage $storage + ) { + $this->resultJsonFactory = $resultFactory; + $this->getAssetById = $getAssetById; + $this->images = $images; + $this->storage = $storage; + parent::__construct($context); + } + + /** + * Execute pages search. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $assetIds = $this->getRequest()->getParam('ids'); + + if (!is_array($assetIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + $assets = $this->getAssetById->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 + ]; + } + + return $this->resultJsonFactory->create()->setData($options); + } +} 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 e8dc232584adb..190a99cac55b6 100644 --- a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php +++ b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php @@ -16,9 +16,6 @@ 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 @@ -35,21 +32,6 @@ class Asset extends Select */ private $getAssetsByIds; - /** - * @var Images - */ - private $images; - - /** - * @var Storage - */ - private $storage; - - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - /** * Constructor * @@ -58,11 +40,6 @@ class Asset extends Select * @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) @@ -74,10 +51,6 @@ public function __construct( FilterModifier $filterModifier, OptionSourceInterface $optionsProvider = null, GetContentByAssetIdsInterface $getContentIdentities, - GetAssetsByIdsInterface $getAssetsByIds, - BookmarkManagementInterface $bookmarkManagement, - Images $images, - Storage $storage, array $components = [], array $data = [] ) { @@ -93,89 +66,6 @@ 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)); } /** @@ -191,7 +81,7 @@ public function applyFilter() $assetIds = $this->filterData[$this->getName()]; if (!is_array($assetIds)) { - $assetIds = $this->stringToArray($assetIds); + $assetIds = explode(',', str_replace(['[', ']'], '', $assetIds)); } $filter = $this->filterBuilder->setConditionType('in') diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml index 86c8590bb4860..20988fad5ff35 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="searchOptions" xsi:type="boolean">true</item> <item name="filterOptions" xsi:type="boolean">true</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml index 58881a8c9de6c..4abeb71679bde 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="filterPlaceholder" xsi:type="string" translate="true">Asset Title</item> <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find assets</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml index 2b7d9fde3b9ff..4634652e9cc19 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="filterPlaceholder" xsi:type="string" translate="true">Asset Title</item> <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find assets</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> From 2f23b48a2d36b2cceec499758362f58069a1b30d Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Aug 2020 19:12:51 +0300 Subject: [PATCH 80/95] Introduce ui-select filter with validation init values --- .../web/js/grid/filters/elements/ui-select.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js new file mode 100644 index 0000000000000..c45ec348103c1 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js @@ -0,0 +1,82 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/ui-select', + 'jquery', + 'underscore' +], function (Select, $, _) { + 'use strict'; + + return Select.extend({ + defaults: { + bookmarkProvider: 'ns = ${ $.ns }, index = bookmarks', + filterChipsProvider: 'componentType = filters, ns = ${ $.ns }', + validationUrl: false, + loadedOption: [], + validationLoading: true, + imports: { + activeIndex: '${ $.bookmarkProvider }:activeIndex' + }, + modules: { + filterChips: '${ $.filterChipsProvider }' + }, + listens: { + activeIndex: 'validateInitialValue' + } + + }, + + /** @inheritdoc */ + initialize: function () { + this._super(); + + this.validateInitialValue(); + + return this; + }, + + /** + * Validate initial value actually exists + */ + validateInitialValue: function () { + if (!_.isEmpty(this.value())) { + $.ajax({ + url: this.validationUrl, + type: 'GET', + dataType: 'json', + context: this, + data: { + ids: this.value() + }, + + /** @param {Object} response */ + success: function (response) { + if (!_.isEmpty(response)) { + this.options([]); + this.success({ + options: response + }); + } + this.filterChips().updateActive(); + }, + + /** set empty array if error occurs */ + error: function () { + this.options([]); + }, + + /** stop loader */ + complete: function () { + this.validationLoading(false); + this.setCaption(); + } + }); + } else { + this.validationLoading(false); + } + } + }); +}); From d99fd2b5bcaddea5cf6a49a17ccbf4fcbe60b3f7 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Thu, 27 Aug 2020 17:48:12 +0100 Subject: [PATCH 81/95] Fixed typo --- .../MediaGallerySynchronization/Model/CreateAssetFromFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 5bfc2fd54ed2a..b4c360c3e0538 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -69,7 +69,7 @@ public function __construct( } /** - * @inheridoc + * @inheritdoc */ public function execute(string $path): AssetInterface { From 13ada4f367f75ef911ac5ea1fb409540e99e7a26 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Aug 2020 22:03:46 +0300 Subject: [PATCH 82/95] Codereview suggestions, static test fixes --- .../Adminhtml/Product/GetSelected.php | 82 ++++++++---------- .../Adminhtml/Product/GetSelected.php | 84 +++++++++++++++++++ .../ui_component/media_gallery_listing.xml | 2 +- .../standalone_media_gallery_listing.xml | 2 +- .../ui_component/media_gallery_listing.xml | 2 - .../Ui/Component/Listing/Filters/Asset.php | 7 +- .../web/js/grid/filters/elements/ui-select.js | 65 +++++++------- 7 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php index c6e39b048c40b..841715180a229 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GetSelected.php @@ -8,77 +8,67 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; -use Magento\Framework\Controller\ResultInterface; -use Magento\Backend\App\Action\Context; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Framework\Controller\Result\JsonFactory; -use Magento\Framework\App\Action\HttpGetActionInterface; -use Magento\Backend\App\Action; +use Magento\Catalog\Api\Data\ProductInterface; -/** - * Returns selected product by product id. for ui-select filter - */ -class GetSelected extends Action implements HttpGetActionInterface +/** Returns information about selected product by product id. Returns empty array if product don't exist */ +class GetSelected extends \Magento\Backend\App\Action { /** + * Authorization level of a basic admin session + * * @see _isAllowed() */ const ADMIN_RESOURCE = 'Magento_Catalog::products'; /** - * @var JsonFactory + * @var \Magento\Framework\Controller\Result\JsonFactory */ private $resultJsonFactory; /** - * @var ProductRepositoryInterface + * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */ - private $productRepository; + private $productCollectionFactory; /** - * GetSelected constructor. - * - * @param JsonFactory $jsonFactory - * @param ProductRepositoryInterface $productRepository - * @param Context $context + * Search constructor. + * @param \Magento\Framework\Controller\Result\JsonFactory $jsonFactory + * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory + * @param \Magento\Backend\App\Action\Context $context */ public function __construct( - JsonFactory $jsonFactory, - ProductRepositoryInterface $productRepository, - Context $context + \Magento\Framework\Controller\Result\JsonFactory $jsonFactory, + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, + \Magento\Backend\App\Action\Context $context ) { $this->resultJsonFactory = $jsonFactory; - $this->productRepository = $productRepository; + $this->productCollectionFactory = $productCollectionFactory; parent::__construct($context); } /** - * - * @return ResultInterface + * @return \Magento\Framework\Controller\ResultInterface */ - public function execute() : ResultInterface + public function execute() : \Magento\Framework\Controller\ResultInterface { - $productIds = $this->getRequest()->getParam('ids'); - $options = []; - - - if (!is_array($productIds)) { - return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + $productId = $this->getRequest()->getParam('productId'); + /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ + $productCollection = $this->productCollectionFactory->create(); + $productCollection->addAttributeToSelect(ProductInterface::NAME); + $productCollection->addIdFilter($productId); + $option = []; + /** @var ProductInterface $product */ + if (!empty($productCollection->getFirstItem()->getData())) { + $product = $productCollection->getFirstItem(); + $option = [ + 'value' => $productId, + 'label' => $product->getName(), + 'is_active' => $product->getStatus(), + 'path' => $product->getSku(), + ]; } - foreach ($productIds as $id) { - try { - $product = $this->productRepository->getById($id); - $options[] = [ - 'value' => $product->getId(), - 'label' => $product->getName(), - 'is_active' => $product->getSatus(), - 'path' => $product->getSku() - ]; - } catch (\Exception $e) { - continue; - } - } - - return $this->resultJsonFactory->create()->setData($options); + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData($option); } } diff --git a/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php new file mode 100644 index 0000000000000..3fb3234482efd --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaGalleryCatalogUi\Controller\Adminhtml\Product; + +use Magento\Framework\Controller\ResultInterface; +use Magento\Backend\App\Action\Context; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Backend\App\Action; + +/** + * Returns selected product by product id. for ui-select filter + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Catalog::products'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * GetSelected constructor. + * + * @param JsonFactory $jsonFactory + * @param ProductRepositoryInterface $productRepository + * @param Context $context + */ + public function __construct( + JsonFactory $jsonFactory, + ProductRepositoryInterface $productRepository, + Context $context + ) { + $this->resultJsonFactory = $jsonFactory; + $this->productRepository = $productRepository; + parent::__construct($context); + } + + /** + * + * @return ResultInterface + */ + public function execute() : ResultInterface + { + $productIds = $this->getRequest()->getParam('ids'); + $options = []; + + + if (!is_array($productIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($productIds as $id) { + try { + $product = $this->productRepository->getById($id); + $options[] = [ + 'value' => $product->getId(), + 'label' => $product->getName(), + 'is_active' => $product->getSatus(), + 'path' => $product->getSku() + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml index a5491e3450451..6976584c2e36c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -29,7 +29,7 @@ <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> <item name="levelsVisibility" xsi:type="number">1</item> <item name="searchUrl" xsi:type="url" path="catalog/product/search"/> - <item name="validationUrl" xsi:type="url" path="catalog/product/getSelected"/> + <item name="validationUrl" xsi:type="url" path="media_gallery_catalog/product/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index a5491e3450451..6976584c2e36c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -29,7 +29,7 @@ <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> <item name="levelsVisibility" xsi:type="number">1</item> <item name="searchUrl" xsi:type="url" path="catalog/product/search"/> - <item name="validationUrl" xsi:type="url" path="catalog/product/getSelected"/> + <item name="validationUrl" xsi:type="url" path="media_gallery_catalog/product/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml index 402866852711d..e49ba7a98c8ce 100644 --- a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -13,7 +13,6 @@ name="page_id" provider="${ $.parentName }" sortOrder="120" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInPages" component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> @@ -39,7 +38,6 @@ name="block_id" provider="${ $.parentName }" sortOrder="130" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInBlocks" component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> 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 190a99cac55b6..f61e34512bfe3 100644 --- a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php +++ b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php @@ -15,7 +15,6 @@ use Magento\MediaContentApi\Api\GetContentByAssetIdsInterface; use Magento\Ui\Component\Filters\FilterModifier; use Magento\Ui\Component\Filters\Type\Select; -use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; /** * Asset filter @@ -27,11 +26,6 @@ class Asset extends Select */ private $getContentIdentities; - /** - * @var GetAssetsByIdsInterface - */ - private $getAssetsByIds; - /** * Constructor * @@ -40,6 +34,7 @@ class Asset extends Select * @param FilterBuilder $filterBuilder * @param FilterModifier $filterModifier * @param OptionSourceInterface $optionsProvider + * @param GetContentByAssetIdsInterface $getContentIdentities * @param array $components * @param array $data * @SuppressWarnings(PHPMD.ExcessiveParameterList) diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js index c45ec348103c1..5e46360b59e7d 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js @@ -42,41 +42,44 @@ define([ * Validate initial value actually exists */ validateInitialValue: function () { - if (!_.isEmpty(this.value())) { - $.ajax({ - url: this.validationUrl, - type: 'GET', - dataType: 'json', - context: this, - data: { - ids: this.value() - }, + if (_.isEmpty(this.value())) { + this.validationLoading(false); - /** @param {Object} response */ - success: function (response) { - if (!_.isEmpty(response)) { - this.options([]); - this.success({ - options: response - }); - } - this.filterChips().updateActive(); - }, + return; + } - /** set empty array if error occurs */ - error: function () { - this.options([]); - }, + $.ajax({ + url: this.validationUrl, + type: 'GET', + dataType: 'json', + context: this, + data: { + ids: this.value() + }, - /** stop loader */ - complete: function () { - this.validationLoading(false); - this.setCaption(); + /** @param {Object} response */ + success: function (response) { + if (!_.isEmpty(response)) { + this.options([]); + this.success({ + options: response + }); } - }); - } else { - this.validationLoading(false); - } + this.filterChips().updateActive(); + }, + + /** set empty array if error occurs */ + error: function () { + this.options([]); + }, + + /** stop loader */ + complete: function () { + this.validationLoading(false); + this.setCaption(); + } + }); + } }); }); From f992f2a4d9ac8f6e845fa1f688c6a4e76535396b Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Aug 2020 23:14:51 +0300 Subject: [PATCH 83/95] Fix static tests --- .../Controller/Adminhtml/Product/GetSelected.php | 2 +- .../Controller/Adminhtml/Block/GetSelected.php | 2 +- .../Controller/Adminhtml/Page/GetSelected.php | 2 +- app/code/Magento/MediaGalleryCmsUi/composer.json | 3 +-- .../Controller/Adminhtml/Asset/GetSelected.php | 2 +- .../Ui/view/base/web/js/grid/filters/elements/ui-select.js | 6 +++++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php index 3fb3234482efd..f70d4584547a3 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php @@ -53,6 +53,7 @@ public function __construct( } /** + * Return selected products options * * @return ResultInterface */ @@ -61,7 +62,6 @@ public function execute() : ResultInterface $productIds = $this->getRequest()->getParam('ids'); $options = []; - if (!is_array($productIds)) { return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); } diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php index a8beeb791f74a..a686f0e7b3ace 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php @@ -52,7 +52,7 @@ public function __construct( } /** - * Execute pages search. + * Return selected blocks options. * * @return ResultInterface */ diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php index 33f87b93c01e1..be6eb9fd9de9f 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php @@ -52,7 +52,7 @@ public function __construct( } /** - * Execute pages search. + * Return selected pages options. * * @return ResultInterface */ diff --git a/app/code/Magento/MediaGalleryCmsUi/composer.json b/app/code/Magento/MediaGalleryCmsUi/composer.json index 73747a669c051..1ecfb9a3c8855 100644 --- a/app/code/Magento/MediaGalleryCmsUi/composer.json +++ b/app/code/Magento/MediaGalleryCmsUi/composer.json @@ -5,8 +5,7 @@ "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-cms": "*", - "magento/module-backend": "*", - "magento/module-ui": "*" + "magento/module-backend": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php index 725c67185ce77..09837c301c367 100644 --- a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php @@ -71,7 +71,7 @@ public function __construct( } /** - * Execute pages search. + * Return selected asset options. * * @return ResultInterface */ diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js index 5e46360b59e7d..9f14639194fa7 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js @@ -29,7 +29,11 @@ define([ }, - /** @inheritdoc */ + /** + * Initializes UiSelect component. + * + * @returns {UiSelect} Chainable. + */ initialize: function () { this._super(); From 322f917ec5c7a4acad9358cb62a217e2950eedf4 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Aug 2020 23:15:36 +0300 Subject: [PATCH 84/95] remove empty line --- .../Ui/view/base/web/js/grid/filters/elements/ui-select.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js index 9f14639194fa7..a913f3fa4a042 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js @@ -83,7 +83,6 @@ define([ this.setCaption(); } }); - } }); }); From aabcdcc0a0e4143fb34b9793a13b09a76969e912 Mon Sep 17 00:00:00 2001 From: joweecaquicla <joie@abovethefray.io> Date: Fri, 28 Aug 2020 18:02:33 +0800 Subject: [PATCH 85/95] magento/adobe-stock-integration#1776: [MFTF] Sorting in media gallery - separated the tests for each sort by options and added new action group --- ...nEnhancedMediaGallerySortByActionGroup.xml | 41 +++++++++ ...diaGallerySortByDirectoryAscendingTest.xml | 85 ++++++++++++++++++ ...iaGallerySortByDirectoryDescendingTest.xml | 86 +++++++++++++++++++ .../AdminMediaGallerySortByNameAToZTest.xml | 85 ++++++++++++++++++ .../AdminMediaGallerySortByNameZToATest.xml | 85 ++++++++++++++++++ ...dminMediaGallerySortByNewestFirstTest.xml} | 39 +++------ ...AdminMediaGallerySortByOldestFirstTest.xml | 84 ++++++++++++++++++ 7 files changed, 476 insertions(+), 29 deletions(-) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml rename app/code/Magento/MediaGalleryUi/Test/Mftf/Test/{AdminMediaGallerySortingTest.xml => AdminMediaGallerySortByNewestFirstTest.xml} (63%) create mode 100644 app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml new file mode 100644 index 0000000000000..451ef81f0ff9f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml @@ -0,0 +1,41 @@ +<?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="AssertAdminEnhancedMediaGallerySortByActionGroup"> + <annotations> + <description>Assert the images position in the grid after sorting has been applied.</description> + </annotations> + <arguments> + <argument name="firstImageFile" type="string"/> + <argument name="secondImageFile" type="string"/> + <argument name="thirdImageFile" type="string"/> + </arguments> + + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" + stepKey="getFirstImageSrcAfterSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" + stepKey="getSecondImageSrcAfterSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" + stepKey="getThirdImageSrcAfterSort"/> + + <assertStringContainsString stepKey="assertFirstImagePositionAfterSort"> + <actualResult type="string">{$getFirstImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{firstImageFile}}</expectedResult> + </assertStringContainsString> + <assertStringContainsString stepKey="assertSecondImagePositionAfterSort"> + <actualResult type="string">{$getSecondImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{secondImageFile}}</expectedResult> + </assertStringContainsString> + <assertStringContainsString stepKey="assertThirdImagePositionAfterSort"> + <actualResult type="string">{$getThirdImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{thirdImageFile}}</expectedResult> + </assertStringContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml new file mode 100644 index 0000000000000..4dbf3da0752b2 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortByDirectoryAscendingTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <stories value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByDirectoryAscending"> + <argument name="sortName" value="directory_asc"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByDirectoryAscending"> + <argument name="firstImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml new file mode 100644 index 0000000000000..025da24511b73 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortByDirectoryDescendingTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <stories value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByDirectoryDescending"> + <argument name="sortName" value="directory_desc"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByDirectoryDescending"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload_1.file}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml new file mode 100644 index 0000000000000..da0d8a18b75e4 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortByNameAToZTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <stories value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNameAToZ"> + <argument name="sortName" value="name_az"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNameAToZ"> + <argument name="firstImageFile" value="{{ImageUpload.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml new file mode 100644 index 0000000000000..4b5086e5d63ff --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortByNameZToATest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <stories value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNameZToA"> + <argument name="sortName" value="name_za"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNameZToA"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload.file}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml similarity index 63% rename from app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml rename to app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml index 395cf6140345d..4274b26d5770f 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortingTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml @@ -7,14 +7,14 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminMediaGallerySortingTest"> + <test name="AdminMediaGallerySortByNewestFirstTest"> <annotations> <features value="MediaGallery"/> <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> - <title value="User uses Sorting in Standalone Media Gallery"/> - <stories value="User uses Sorting in Standalone Media Gallery"/> + <title value="User uses Sort by Newest First in Standalone Media Gallery"/> + <stories value="User uses Sort by Newest First in Standalone Media Gallery"/> <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> - <description value="User uses Sorting in Standalone Media Gallery"/> + <description value="User uses Sort by Newest First in Standalone Media Gallery"/> <severity value="CRITICAL"/> <group value="media_gallery_ui"/> </annotations> @@ -67,37 +67,18 @@ <argument name="name" value="secondFolder"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> - <argument name="image" value="ImageUpload3"/> + <argument name="image" value="ImageUpload1"/> </actionGroup> <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> <waitForPageLoad stepKey="secondWaitForGridToLoad"/> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" - stepKey="getFirstImageBeforeNewestFirstSort"/> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" - stepKey="getSecondImageBeforeNewestFirstSort"/> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" - stepKey="getThirdImageBeforeNewestFirstSort"/> <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNewestFirst"> <argument name="sortName" value="newest_first"/> </actionGroup> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" - stepKey="getFirstImageAfterNewestFirstSort"/> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" - stepKey="getSecondImageAfterNewestFirstSort"/> - <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" - stepKey="getThirdImageAfterNewestFirstSort"/> - <assertEquals stepKey="assertFirstImagePositionAfterNewestFirstSort"> - <actualResult type="string">{$getFirstImageAfterNewestFirstSort}</actualResult> - <expectedResult type="string">{$getFirstImageBeforeNewestFirstSort}</expectedResult> - </assertEquals> - <assertEquals stepKey="assertSecondImagePositionAfterNewestFirstSort"> - <actualResult type="string">{$getSecondImageAfterNewestFirstSort}</actualResult> - <expectedResult type="string">{$getSecondImageBeforeNewestFirstSort}</expectedResult> - </assertEquals> - <assertEquals stepKey="assertThirdImagePositionAfterNewestFirstSort"> - <actualResult type="string">{$getThirdImageAfterNewestFirstSort}</actualResult> - <expectedResult type="string">{$getThirdImageBeforeNewestFirstSort}</expectedResult> - </assertEquals> + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNewestFirst"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload.file}}"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml new file mode 100644 index 0000000000000..e67fdcfcf40b3 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * 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="AdminMediaGallerySortByOldestFirstTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <stories value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByOldestFirst"> + <argument name="sortName" value="oldest_first"/> + </actionGroup> + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByOldestFirst"> + <argument name="firstImageFile" value="{{ImageUpload.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> From 6f1f676f4c01d803a202ad34c955282c06f41099 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Fri, 28 Aug 2020 11:23:37 +0100 Subject: [PATCH 86/95] magento/magento2#29677: Corrected property access level --- .../Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index 1266dcc7c035c..c7b0752e52181 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -23,7 +23,7 @@ class OnInsert extends Images implements HttpPostActionInterface /** * @var GetInsertImageContent */ - protected $getInsertImageContent; + private $getInsertImageContent; /** * @param Context $context From eb52f1c74ac15e6186845855239d3f582a264a06 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Fri, 28 Aug 2020 14:43:48 +0300 Subject: [PATCH 87/95] Add elasticsearch params to integration tests config --- dev/tests/integration/etc/install-config-mysql.php.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/tests/integration/etc/install-config-mysql.php.dist b/dev/tests/integration/etc/install-config-mysql.php.dist index 4766048c62375..1d4b3d1951e32 100644 --- a/dev/tests/integration/etc/install-config-mysql.php.dist +++ b/dev/tests/integration/etc/install-config-mysql.php.dist @@ -11,6 +11,9 @@ return [ 'db-name' => 'magento_integration_tests', 'db-prefix' => '', 'backend-frontname' => 'backend', + 'search-engine' => 'elasticsearch7', + 'elasticsearch-host' => 'localhost', + 'elasticsearch-port' => 9200, 'admin-user' => \Magento\TestFramework\Bootstrap::ADMIN_NAME, 'admin-password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, 'admin-email' => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL, From a5ee11043ffe8f1d978895db423d7590cd3fe2b3 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 28 Aug 2020 14:38:49 -0500 Subject: [PATCH 88/95] MC-36456: union implementation --- .../{UnionType1.php => TestUnion.php} | 19 ++----------------- .../etc/schema.graphqls | 4 ++-- .../GraphQl/TestModule/GraphQlQueryTest.php | 11 ----------- .../Output/ElementMapper/Formatter/Fields.php | 19 +++++++++++-------- .../ElementMapper/FormatterComposite.php | 2 +- .../GraphQlSchemaStitching/GraphQlReader.php | 15 ++++++++------- 6 files changed, 24 insertions(+), 46 deletions(-) rename dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/{UnionType1.php => TestUnion.php} (59%) diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php similarity index 59% rename from dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php rename to dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php index e012178d0ce22..592b0caaa88a3 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionType1.php +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php @@ -10,27 +10,12 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface; /** - * Resolver for Union Type 1 + * Resolver for Union type TestUnion */ -class UnionType1 implements ResolverInterface +class TestUnion implements ResolverInterface { - /** - * @var CollectionFactoryInterface - */ - private $collectionFactory; - - /** - * @param CollectionFactoryInterface $collectionFactory - */ - public function __construct( - CollectionFactoryInterface $collectionFactory - ) { - $this->collectionFactory = $collectionFactory; - } - /** * @inheritDoc */ diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls index 09050085bc0a2..1a5796e07b08b 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls @@ -3,7 +3,7 @@ type Query { testItem(id: Int!) : Item @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\Item") - testUnion: UnionType1 @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionType1") + testUnion: TestUnion @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\TestUnion") } type Mutation { @@ -20,7 +20,7 @@ type MutationItem { name: String } -union UnionType1 @doc(description: "some kind of union") @typeResolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionTypeResolver") = +union TestUnion @doc(description: "some kind of union") @typeResolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionTypeResolver") = TypeCustom1 | TypeCustom2 type TypeCustom1 { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index 7d2d6219e0272..062f3d70d6a4f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -107,11 +107,6 @@ public function testQueryTestUnionResults() $query = <<<QUERY { - testItem(id: {$id}) - { - item_id - name - } testUnion { __typename ... on TypeCustom1 { @@ -125,12 +120,6 @@ public function testQueryTestUnionResults() QUERY; $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('testItem', $response); - $testItem = $response['testItem']; - $this->assertArrayHasKey('item_id', $testItem); - $this->assertArrayHasKey('name', $testItem); - $this->assertEquals(1, $testItem['item_id']); - $this->assertEquals('itemName', $testItem['name']); $this->assertArrayHasKey('testUnion', $response); $testUnion = $response['testUnion']; diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php index 7ff2ea60bd2cc..3456f3c039a64 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php @@ -92,15 +92,18 @@ public function __construct( */ public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { - $typeConfig = [ - 'fields' => function () use ($configElement, $outputType) { - $fieldsConfig = []; - foreach ($configElement->getFields() as $field) { - $fieldsConfig[$field->getName()] = $this->getFieldConfig($configElement, $outputType, $field); + $typeConfig = []; + if ($configElement instanceof TypeInterface) { + $typeConfig = [ + 'fields' => function () use ($configElement, $outputType) { + $fieldsConfig = []; + foreach ($configElement->getFields() as $field) { + $fieldsConfig[$field->getName()] = $this->getFieldConfig($configElement, $outputType, $field); + } + return $fieldsConfig; } - return $fieldsConfig; - } - ]; + ]; + } return $typeConfig; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 99ef725afe32f..c36df11012017 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -42,6 +42,6 @@ public function format(ConfigElementInterface $configElement, OutputTypeInterfac $formattedConfig[] = $formatter->format($configElement, $outputType); } - return array_merge($defaultConfig, ...$formattedConfig); + return array_merge($defaultConfig, ...$formattedConfig); } } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index 34913ae949f28..cf1477ee81941 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -330,13 +330,14 @@ private static function getModuleNameForRelevantFile(string $file): string */ private function addModuleNameToTypes(array $source, string $filePath): array { - foreach ($source as $typeName => $type) { - if ((!isset($type['module'])) - && (($type['type'] ?? '' === InterfaceType::GRAPHQL_INTERFACE && isset($type['typeResolver'])) - || isset($type['implements']) - ) - ) { - $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); + foreach ($source as $typeName => $typeDefinition) { + if (!isset($typeDefinition['module'])) { + $hasTypeResolver = (bool)$typeDefinition['typeResolver'] ?? false; + $hasImplements = (bool)$typeDefinition['implements'] ?? false; + $typeDefinition = (bool)$typeDefinition['type'] ?? false; + if ((($typeDefinition === InterfaceType::GRAPHQL_INTERFACE && $hasTypeResolver) || $hasImplements)) { + $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); + } } } From 140159728a1477615168c023c5eb67b91929a90b Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 28 Aug 2020 16:47:39 -0500 Subject: [PATCH 89/95] MC-36456: union implementation --- .../testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index 062f3d70d6a4f..0bbdf5a4c9803 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -103,8 +103,6 @@ public function testQueryViaGetRequestWithVariablesReturnsResults() public function testQueryTestUnionResults() { - $id = 1; - $query = <<<QUERY { testUnion { From 13a58069262447ca38d1d7671fa73ab32415a464 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 28 Aug 2020 17:08:15 -0500 Subject: [PATCH 90/95] MC-36456: union implementation --- .../Framework/GraphQlSchemaStitching/GraphQlReader.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index cf1477ee81941..9845646e09fd5 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -332,9 +332,9 @@ private function addModuleNameToTypes(array $source, string $filePath): array { foreach ($source as $typeName => $typeDefinition) { if (!isset($typeDefinition['module'])) { - $hasTypeResolver = (bool)$typeDefinition['typeResolver'] ?? false; - $hasImplements = (bool)$typeDefinition['implements'] ?? false; - $typeDefinition = (bool)$typeDefinition['type'] ?? false; + $hasTypeResolver = (bool)($typeDefinition['typeResolver'] ?? false); + $hasImplements = (bool)($typeDefinition['implements'] ?? false); + $typeDefinition = (bool)($typeDefinition['type'] ?? false); if ((($typeDefinition === InterfaceType::GRAPHQL_INTERFACE && $hasTypeResolver) || $hasImplements)) { $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); } From 9ff665b3e782833385363eb3c860c489031ab3cc Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Mon, 31 Aug 2020 16:28:46 +0100 Subject: [PATCH 91/95] Updated composer.lock --- composer.lock | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 36a42d0c750df..551167152be4d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "197f0388c574f9d40555a95634e439e0", + "content-hash": "aadcf8a265dd7ecbb86dd3dd4e49bc28", "packages": [ { "name": "colinmollenhour/cache-backend-file", @@ -8120,12 +8120,6 @@ "sftp", "storage" ], - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } - ], "time": "2020-05-18T15:13:39+00:00" }, { From 6bb128b5c30670bf365017e16cedf15cc23813ba Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 31 Aug 2020 12:31:39 -0500 Subject: [PATCH 92/95] MC-37041: MFTF StorefrontAddStoreCodeInUrl fails when using Varnish --- .../Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml | 3 +++ ...torefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml index 898c26bb4b45a..cea228ac7a344 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml @@ -16,6 +16,9 @@ <description value="Users are able to place order using Paypal Smart Button on Checkout Page, payment action is Sale"/> <severity value="CRITICAL"/> <testCaseId value="MC-13690"/> + <skip> + <issueId value="MC-37236"/> + </skip> <group value="paypalExpress"/> </annotations> <before> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml index 3fd5f44d5a4b6..a4d99ecbf7e61 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml @@ -16,6 +16,9 @@ <description value="Users are able to place order using Paypal Smart Button using Euro currency and merchant country is France"/> <severity value="MAJOR"/> <testCaseId value="MC-33274"/> + <skip> + <issueId value="MC-37236"/> + </skip> <group value="paypalExpress"/> </annotations> <before> From 165aa5ca4724b81afeab5dc9119d055b0c60e8a3 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Mon, 31 Aug 2020 21:10:50 +0100 Subject: [PATCH 93/95] Skipped flacky tests --- .../Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml | 3 +++ ...inMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml index c3f3d6ecd9e8d..d2a04a7d21d11 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml @@ -9,6 +9,9 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMediaGalleryCatalogUiUsedInProductFilterTest"> <annotations> + <skip> + <issueId value="https://github.com/magento/adobe-stock-integration/issues/1795"/> + </skip> <features value="AdminMediaGalleryUsedInProductsFilter"/> <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1503"/> <title value="User can open product entity the asset is associated"/> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml index 51cac8e4a0a6b..8191d5570f1e8 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -9,6 +9,9 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest"> <annotations> + <skip> + <issueId value="https://github.com/magento/adobe-stock-integration/issues/1794"/> + </skip> <features value="AdminMediaGalleryCategoryGrid"/> <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1503"/> <title value="User can open each entity the asset is associated with in a separate tab to manage association"/> From e2cde9b3e28aff1822ba427b702bce2948a68f7d Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 1 Sep 2020 09:19:08 +0100 Subject: [PATCH 94/95] Skipped flacky test --- .../Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml index 3dd294fa50605..5faa1e67bc596 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml @@ -9,6 +9,9 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMediaGalleryUploadCategoryImageTest"> <annotations> + <skip> + <issueId value="https://github.com/magento/adobe-stock-integration/issues/1796"/> + </skip> <features value="AdminMediaGalleryImagePanel"/> <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1435"/> <stories value="User uploads image outside of the Media Gallery"/> From ab2782f5a637be5f8319058e62850d45468b0c75 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Tue, 1 Sep 2020 20:20:23 +0100 Subject: [PATCH 95/95] magento/magento2#29677: Returned existing behaviour --- .../Model/Wysiwyg/Images/GetInsertImageContent.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php index 3531bf7569e38..305d73fff4dc7 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -8,6 +8,7 @@ namespace Magento\Cms\Model\Wysiwyg\Images; +use Magento\Catalog\Helper\Data as CatalogHelper; use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; class GetInsertImageContent @@ -18,13 +19,18 @@ class GetInsertImageContent private $imagesHelper; /** - * PrepareImage constructor. - * + * @var CatalogHelper + */ + private $catalogHelper; + + /** * @param ImagesHelper $imagesHelper + * @param CatalogHelper $catalogHelper */ - public function __construct(ImagesHelper $imagesHelper) + public function __construct(ImagesHelper $imagesHelper, CatalogHelper $catalogHelper) { $this->imagesHelper = $imagesHelper; + $this->catalogHelper = $catalogHelper; } /** @@ -44,6 +50,7 @@ public function execute( ): string { $filename = $this->imagesHelper->idDecode($encodedFilename); + $this->catalogHelper->setStoreId($storeId); $this->imagesHelper->setStoreId($storeId); if ($forceStaticPath) {