From ca9e58264092cc56b993f65789e162612a7d5c9e Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 20 Apr 2017 23:16:22 +0200 Subject: [PATCH 001/236] Zend_Feed refactoring progress --- app/code/Magento/Rss/Model/Rss.php | 12 +++- .../Controller/Adminhtml/Feed/IndexTest.php | 19 +++++- .../Test/Unit/Controller/Feed/IndexTest.php | 21 +++++-- app/etc/di.xml | 2 + lib/internal/Magento/Framework/App/Feed.php | 32 ++++++++++ .../Magento/Framework/App/Feed/Importer.php | 58 +++++++++++++++++++ .../Framework/App/FeedImporterInterface.php | 18 ++++++ .../Magento/Framework/App/FeedInterface.php | 14 +++++ .../Exception/FeedImporterException.php | 13 +++++ 9 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 lib/internal/Magento/Framework/App/Feed.php create mode 100644 lib/internal/Magento/Framework/App/Feed/Importer.php create mode 100644 lib/internal/Magento/Framework/App/FeedImporterInterface.php create mode 100644 lib/internal/Magento/Framework/App/FeedInterface.php create mode 100644 lib/internal/Magento/Framework/Exception/FeedImporterException.php diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index 057ebab432aaa..b73db8b1535b1 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -21,6 +21,11 @@ class Rss */ protected $cache; + /** + * @var \Magento\Framework\App\FeedImporterInterface + */ + private $feedImporter; + /** * @var SerializerInterface */ @@ -30,13 +35,16 @@ class Rss * Rss constructor * * @param \Magento\Framework\App\CacheInterface $cache + * @param \Magento\Framework\App\FeedImporterInterface $feedImporter * @param SerializerInterface|null $serializer */ public function __construct( \Magento\Framework\App\CacheInterface $cache, + \Magento\Framework\App\FeedImporterInterface $feedImporter, SerializerInterface $serializer = null ) { $this->cache = $cache; + $this->feedImporter = $feedImporter; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); } @@ -86,7 +94,7 @@ public function setDataProvider(DataProviderInterface $dataProvider) */ public function createRssXml() { - $rssFeedFromArray = \Zend_Feed::importArray($this->getFeeds(), 'rss'); - return $rssFeedFromArray->saveXML(); + $rssFeed = $this->feedImporter->importArray($this->getFeeds(), 'rss'); + return $rssFeed->asXML(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 0495212cfba71..209f393212af1 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -102,15 +102,28 @@ public function testExecuteWithException() $this->scopeConfigInterface->expects($this->once())->method('getValue')->will($this->returnValue(true)); $dataProvider = $this->getMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); + $dataProvider->expects($this->once())->method('getRssData')->will($this->returnValue([])); - $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider'], [], '', false); - $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + $objectManagerHelper = new ObjectManagerHelper($this); + $feedImporter = $objectManagerHelper->getObject( + \Magento\Framework\App\Feed\Importer::class, + [ + 'feedProcessor' => $objectManagerHelper->getObject(\Zend_Feed::class), + ] + ); + + $rssModel = $objectManagerHelper->getObject( + \Magento\Rss\Model\Rss::class, + [ + 'feedImporter' => $feedImporter, + ] + ); $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException('\Zend_Feed_Builder_Exception'); + $this->setExpectedException('\Magento\Framework\Exception\FeedImporterException'); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index a8ca05af3a68d..21ae5ec6822df 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -52,6 +52,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $objectManagerHelper = new ObjectManagerHelper($this); + $this->controller = $objectManagerHelper->getObject( \Magento\Rss\Controller\Feed\Index::class, [ @@ -89,15 +90,27 @@ public function testExecuteWithException() $this->scopeConfigInterface->expects($this->once())->method('getValue')->will($this->returnValue(true)); $dataProvider = $this->getMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); - - $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider'], [], '', false); - $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + $dataProvider->expects($this->once())->method('getRssData')->will($this->returnValue([])); + + $objectManagerHelper = new ObjectManagerHelper($this); + $feedImporter = $objectManagerHelper->getObject( + \Magento\Framework\App\Feed\Importer::class, + [ + 'feedProcessor' => $objectManagerHelper->getObject(\Zend_Feed::class), + ] + ); + $rssModel = $objectManagerHelper->getObject( + \Magento\Rss\Model\Rss::class, + [ + 'feedImporter' => $feedImporter, + ] + ); $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException('\Zend_Feed_Builder_Exception'); + $this->setExpectedException('\Magento\Framework\Exception\FeedImporterException'); $this->controller->execute(); } } diff --git a/app/etc/di.xml b/app/etc/di.xml index 91c008219992e..3643588dbbf25 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -170,6 +170,8 @@ + + diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php new file mode 100644 index 0000000000000..9c976c807a750 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -0,0 +1,32 @@ +feed = $feed; + } + + /** + * Get the xml from Zend_Feed_Abstract object + * + * @return string + */ + public function asXml() + { + return $this->feed->saveXml(); + } +} diff --git a/lib/internal/Magento/Framework/App/Feed/Importer.php b/lib/internal/Magento/Framework/App/Feed/Importer.php new file mode 100644 index 0000000000000..fed94df5751c1 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Feed/Importer.php @@ -0,0 +1,58 @@ +feedProcessor = $feedProcessor; + $this->feedFactory = $feedFactory; + } + + /** + * Get a new \Magento\Framework\App\Feed object from a custom array + * + * @throws \Magento\Framework\Exception\FeedImporterException + * @param array $data + * @param string $format + * @return \Magento\Framework\App\FeedInterface + */ + public function importArray(array $data, $format = 'atom') + { + try { + $feed = $this->feedProcessor->importArray($data, $format); + return $this->feedFactory->create(['feed' => $feed]); + } + catch (\Zend_Feed_Exception $e) { + throw new \Magento\Framework\Exception\FeedImporterException( + new \Magento\Framework\Phrase($e->getMessage()), + $e + ); + } + } +} + + diff --git a/lib/internal/Magento/Framework/App/FeedImporterInterface.php b/lib/internal/Magento/Framework/App/FeedImporterInterface.php new file mode 100644 index 0000000000000..6f276c0caa7dd --- /dev/null +++ b/lib/internal/Magento/Framework/App/FeedImporterInterface.php @@ -0,0 +1,18 @@ + Date: Fri, 21 Apr 2017 16:11:39 +0200 Subject: [PATCH 002/236] Tests refactoring --- .../Controller/Adminhtml/Feed/IndexTest.php | 19 ++++++--------- .../Test/Unit/Controller/Feed/IndexTest.php | 24 ++++++++----------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 209f393212af1..b9c6a80fc4deb 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -102,21 +102,16 @@ public function testExecuteWithException() $this->scopeConfigInterface->expects($this->once())->method('getValue')->will($this->returnValue(true)); $dataProvider = $this->getMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); - $dataProvider->expects($this->once())->method('getRssData')->will($this->returnValue([])); - $objectManagerHelper = new ObjectManagerHelper($this); - $feedImporter = $objectManagerHelper->getObject( - \Magento\Framework\App\Feed\Importer::class, - [ - 'feedProcessor' => $objectManagerHelper->getObject(\Zend_Feed::class), - ] + $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml'], [], '', false); + $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + + $exceptionMock = new \Magento\Framework\Exception\FeedImporterException( + new \Magento\Framework\Phrase('Any message') ); - $rssModel = $objectManagerHelper->getObject( - \Magento\Rss\Model\Rss::class, - [ - 'feedImporter' => $feedImporter, - ] + $rssModel->expects($this->once())->method('createRssXml')->will( + $this->throwException($exceptionMock) ); $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index 21ae5ec6822df..2cf23c9d29edd 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -90,27 +90,23 @@ public function testExecuteWithException() $this->scopeConfigInterface->expects($this->once())->method('getValue')->will($this->returnValue(true)); $dataProvider = $this->getMock(\Magento\Framework\App\Rss\DataProviderInterface::class); $dataProvider->expects($this->once())->method('isAllowed')->will($this->returnValue(true)); - $dataProvider->expects($this->once())->method('getRssData')->will($this->returnValue([])); - - $objectManagerHelper = new ObjectManagerHelper($this); - $feedImporter = $objectManagerHelper->getObject( - \Magento\Framework\App\Feed\Importer::class, - [ - 'feedProcessor' => $objectManagerHelper->getObject(\Zend_Feed::class), - ] + + $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml'], [], '', false); + $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); + + $exceptionMock = new \Magento\Framework\Exception\FeedImporterException( + new \Magento\Framework\Phrase('Any message') ); - $rssModel = $objectManagerHelper->getObject( - \Magento\Rss\Model\Rss::class, - [ - 'feedImporter' => $feedImporter, - ] + + $rssModel->expects($this->once())->method('createRssXml')->will( + $this->throwException($exceptionMock) ); $this->response->expects($this->once())->method('setHeader')->will($this->returnSelf()); $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException('\Magento\Framework\Exception\FeedImporterException'); + $this->setExpectedException(\Magento\Framework\Exception\FeedImporterException::class); $this->controller->execute(); } } From c7ba65a1caeabbbde4a32fa57938811255f34715 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sat, 22 Apr 2017 13:32:40 +0200 Subject: [PATCH 003/236] Fixed unit test --- .../Magento/Rss/Test/Unit/Model/RssTest.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 7674aedf2d3f6..6b5d5e2eab4b5 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -33,6 +33,27 @@ class RssTest extends \PHPUnit_Framework_TestCase ], ]; + /** + * @var string + */ + protected $feedXml = ' + + + <![CDATA[Feed Title]]> + http://magento.com/rss/link + + Sat, 22 Apr 2017 13:21:12 +0200 + Zend_Feed + http://blogs.law.harvard.edu/tech/rss + + <![CDATA[Feed 1 Title]]> + http://magento.com/rss/link/id/1 + + Sat, 22 Apr 2017 13:21:12 +0200 + + +'; + /** * @var ObjectManagerHelper */ @@ -43,6 +64,16 @@ class RssTest extends \PHPUnit_Framework_TestCase */ private $cacheMock; + /** + * @var \Magento\Framework\App\FeedImporterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $feedImporterMock; + + /** + * @var \Magento\Framework\App\FeedInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $feedMock; + /** * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -52,11 +83,15 @@ protected function setUp() { $this->cacheMock = $this->getMock(\Magento\Framework\App\CacheInterface::class); $this->serializerMock = $this->getMock(SerializerInterface::class); + $this->feedImporterMock = $this->getMock(\Magento\Framework\App\FeedImporterInterface::class); + $this->feedMock = $this->getMock(\Magento\Framework\App\FeedInterface::class); + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->rss = $this->objectManagerHelper->getObject( \Magento\Rss\Model\Rss::class, [ 'cache' => $this->cacheMock, + 'feedImporter' => $this->feedImporterMock, 'serializer' => $this->serializerMock ] ); @@ -116,6 +151,15 @@ public function testCreateRssXml() $dataProvider->expects($this->any())->method('getCacheLifetime')->will($this->returnValue(100)); $dataProvider->expects($this->any())->method('getRssData')->will($this->returnValue($this->feedData)); + $this->feedMock->expects($this->once()) + ->method('asXml') + ->will($this->returnValue($this->feedXml)); + + $this->feedImporterMock->expects($this->once()) + ->method('importArray') + ->with($this->feedData) + ->will($this->returnValue($this->feedMock)); + $this->rss->setDataProvider($dataProvider); $result = $this->rss->createRssXml(); $this->assertContains('', $result); From 9d0a290457df41971334906108e976b55c003651 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sat, 22 Apr 2017 13:33:40 +0200 Subject: [PATCH 004/236] Tidying up files --- .../Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php | 2 +- lib/internal/Magento/Framework/App/Feed.php | 4 ++-- lib/internal/Magento/Framework/App/Feed/Importer.php | 8 +++----- .../Magento/Framework/Exception/FeedImporterException.php | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index b9c6a80fc4deb..790ca898d0290 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -118,7 +118,7 @@ public function testExecuteWithException() $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException('\Magento\Framework\Exception\FeedImporterException'); + $this->setExpectedException(\Magento\Framework\Exception\FeedImporterException::class); $this->controller->execute(); } } diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 9c976c807a750..e9bccf4c9ed78 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -15,7 +15,7 @@ class Feed implements \Magento\Framework\App\FeedInterface /** * @param \Zend_Feed_Abstract $feed */ - public function __construct(\Zend_Feed_Abstract $feed) + public function __construct(\Zend_Feed_Abstract $feed) { $this->feed = $feed; } @@ -29,4 +29,4 @@ public function asXml() { return $this->feed->saveXml(); } -} +} \ No newline at end of file diff --git a/lib/internal/Magento/Framework/App/Feed/Importer.php b/lib/internal/Magento/Framework/App/Feed/Importer.php index fed94df5751c1..42bbc90267588 100644 --- a/lib/internal/Magento/Framework/App/Feed/Importer.php +++ b/lib/internal/Magento/Framework/App/Feed/Importer.php @@ -42,11 +42,11 @@ public function __construct( */ public function importArray(array $data, $format = 'atom') { + try { $feed = $this->feedProcessor->importArray($data, $format); - return $this->feedFactory->create(['feed' => $feed]); - } - catch (\Zend_Feed_Exception $e) { + return $this->feedFactory->create(['feed' => $feed]); + } catch (\Zend_Feed_Exception $e) { throw new \Magento\Framework\Exception\FeedImporterException( new \Magento\Framework\Phrase($e->getMessage()), $e @@ -54,5 +54,3 @@ public function importArray(array $data, $format = 'atom') } } } - - diff --git a/lib/internal/Magento/Framework/Exception/FeedImporterException.php b/lib/internal/Magento/Framework/Exception/FeedImporterException.php index 0a197e6bc2ab2..e6faec0a60270 100644 --- a/lib/internal/Magento/Framework/Exception/FeedImporterException.php +++ b/lib/internal/Magento/Framework/Exception/FeedImporterException.php @@ -10,4 +10,4 @@ class FeedImporterException extends LocalizedException { -} \ No newline at end of file +} From 109047fa18803a3922b0572a0aa21e6b14e9a9de Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sat, 22 Apr 2017 14:07:57 +0200 Subject: [PATCH 005/236] Small CS fix --- lib/internal/Magento/Framework/App/Feed.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index e9bccf4c9ed78..044b5c68c516c 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -29,4 +29,5 @@ public function asXml() { return $this->feed->saveXml(); } -} \ No newline at end of file +} + From 4534a2e41f487352b2413d94ab825f2414eba7a7 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sat, 22 Apr 2017 14:49:57 +0200 Subject: [PATCH 006/236] Small CS fix --- lib/internal/Magento/Framework/App/Feed.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 044b5c68c516c..2fab3da33173e 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -30,4 +30,3 @@ public function asXml() return $this->feed->saveXml(); } } - From c413389ffe73799d8faebbece88208c39cdf0f5c Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sat, 29 Apr 2017 18:36:48 +0200 Subject: [PATCH 007/236] More CS fixes --- app/code/Magento/Rss/Test/Unit/Model/RssTest.php | 2 +- lib/internal/Magento/Framework/App/Feed.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 6b5d5e2eab4b5..f922a936a9add 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -85,7 +85,7 @@ protected function setUp() $this->serializerMock = $this->getMock(SerializerInterface::class); $this->feedImporterMock = $this->getMock(\Magento\Framework\App\FeedImporterInterface::class); $this->feedMock = $this->getMock(\Magento\Framework\App\FeedInterface::class); - + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->rss = $this->objectManagerHelper->getObject( \Magento\Rss\Model\Rss::class, diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 2fab3da33173e..1a8fe94db4b1b 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -22,7 +22,7 @@ public function __construct(\Zend_Feed_Abstract $feed) /** * Get the xml from Zend_Feed_Abstract object - * + * * @return string */ public function asXml() From caa4471aede4e4415fe4abb629f28c8e0ef6d954 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Sun, 7 May 2017 15:16:31 +0200 Subject: [PATCH 008/236] Small exception fix --- lib/internal/Magento/Framework/App/Feed/Importer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/App/Feed/Importer.php b/lib/internal/Magento/Framework/App/Feed/Importer.php index 42bbc90267588..20fd1e051fde4 100644 --- a/lib/internal/Magento/Framework/App/Feed/Importer.php +++ b/lib/internal/Magento/Framework/App/Feed/Importer.php @@ -48,7 +48,7 @@ public function importArray(array $data, $format = 'atom') return $this->feedFactory->create(['feed' => $feed]); } catch (\Zend_Feed_Exception $e) { throw new \Magento\Framework\Exception\FeedImporterException( - new \Magento\Framework\Phrase($e->getMessage()), + __($e->getMessage()), $e ); } From 66154e6e95807650591a7ad8b6e049ddd883b919 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Wed, 24 May 2017 17:26:35 +0200 Subject: [PATCH 009/236] Changes after the code review --- app/code/Magento/Rss/Model/Rss.php | 9 +++---- .../Controller/Adminhtml/Feed/IndexTest.php | 4 ++-- .../Test/Unit/Controller/Feed/IndexTest.php | 4 ++-- .../Magento/Rss/Test/Unit/Model/RssTest.php | 4 ++-- .../Magento/Framework/App/Feed/Importer.php | 24 ++++++++++++++----- .../Framework/App/FeedImporterInterface.php | 4 +++- .../Exception/FeedImporterException.php | 13 ---------- 7 files changed, 32 insertions(+), 30 deletions(-) delete mode 100644 lib/internal/Magento/Framework/Exception/FeedImporterException.php diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index b73db8b1535b1..d90b5accbbdf9 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -8,6 +8,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\App\FeedImporterInterface; class Rss { @@ -35,17 +36,17 @@ class Rss * Rss constructor * * @param \Magento\Framework\App\CacheInterface $cache - * @param \Magento\Framework\App\FeedImporterInterface $feedImporter * @param SerializerInterface|null $serializer + * @param FeedImporterInterface|null $feedImporter */ public function __construct( \Magento\Framework\App\CacheInterface $cache, - \Magento\Framework\App\FeedImporterInterface $feedImporter, - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + FeedImporterInterface $feedImporter = null ) { $this->cache = $cache; - $this->feedImporter = $feedImporter; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); + $this->feedImporter = $feedImporter ?: ObjectManager::getInstance()->get(FeedImporterInterface::class); } /** diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 790ca898d0290..b11a1d8f63971 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -106,7 +106,7 @@ public function testExecuteWithException() $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml'], [], '', false); $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); - $exceptionMock = new \Magento\Framework\Exception\FeedImporterException( + $exceptionMock = new \Magento\Framework\Exception\RuntimeException( new \Magento\Framework\Phrase('Any message') ); @@ -118,7 +118,7 @@ public function testExecuteWithException() $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException(\Magento\Framework\Exception\FeedImporterException::class); + $this->setExpectedException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index 2cf23c9d29edd..c8186b6b49427 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -94,7 +94,7 @@ public function testExecuteWithException() $rssModel = $this->getMock(\Magento\Rss\Model\Rss::class, ['setDataProvider', 'createRssXml'], [], '', false); $rssModel->expects($this->once())->method('setDataProvider')->will($this->returnSelf()); - $exceptionMock = new \Magento\Framework\Exception\FeedImporterException( + $exceptionMock = new \Magento\Framework\Exception\RuntimeException( new \Magento\Framework\Phrase('Any message') ); @@ -106,7 +106,7 @@ public function testExecuteWithException() $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->setExpectedException(\Magento\Framework\Exception\FeedImporterException::class); + $this->setExpectedException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index f922a936a9add..f84ded89cdfce 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -19,7 +19,7 @@ class RssTest extends \PHPUnit_Framework_TestCase /** * @var array */ - protected $feedData = [ + private $feedData = [ 'title' => 'Feed Title', 'link' => 'http://magento.com/rss/link', 'description' => 'Feed Description', @@ -36,7 +36,7 @@ class RssTest extends \PHPUnit_Framework_TestCase /** * @var string */ - protected $feedXml = ' + private $feedXml = ' <![CDATA[Feed Title]]> diff --git a/lib/internal/Magento/Framework/App/Feed/Importer.php b/lib/internal/Magento/Framework/App/Feed/Importer.php index 20fd1e051fde4..e0e1ba13ab1e6 100644 --- a/lib/internal/Magento/Framework/App/Feed/Importer.php +++ b/lib/internal/Magento/Framework/App/Feed/Importer.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\App\Feed; +use Magento\Framework\App\FeedFactory; +use Psr\Log\LoggerInterface; + /** * Feed importer */ @@ -16,26 +19,34 @@ class Importer implements \Magento\Framework\App\FeedImporterInterface private $feedProcessor; /** - * @var \Magento\Framework\App\FeedFactory + * @var FeedFactory */ private $feedFactory; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param \Zend_Feed $feedProcessor - * @param \Magento\Framework\App\FeedFactory $feedFactory + * @param FeedFactory $feedFactory + * @param LoggerInterface $logger */ public function __construct( \Zend_Feed $feedProcessor, - \Magento\Framework\App\FeedFactory $feedFactory + FeedFactory $feedFactory, + LoggerInterface $logger ) { $this->feedProcessor = $feedProcessor; $this->feedFactory = $feedFactory; + $this->logger = $logger; } /** * Get a new \Magento\Framework\App\Feed object from a custom array * - * @throws \Magento\Framework\Exception\FeedImporterException + * @throws \Magento\Framework\Exception\RuntimeException * @param array $data * @param string $format * @return \Magento\Framework\App\FeedInterface @@ -47,8 +58,9 @@ public function importArray(array $data, $format = 'atom') $feed = $this->feedProcessor->importArray($data, $format); return $this->feedFactory->create(['feed' => $feed]); } catch (\Zend_Feed_Exception $e) { - throw new \Magento\Framework\Exception\FeedImporterException( - __($e->getMessage()), + $this->logger->error($e->getMessage()); + throw new \Magento\Framework\Exception\RuntimeException( + __('There has been an error with import'), $e ); } diff --git a/lib/internal/Magento/Framework/App/FeedImporterInterface.php b/lib/internal/Magento/Framework/App/FeedImporterInterface.php index 6f276c0caa7dd..cd99b1257b93a 100644 --- a/lib/internal/Magento/Framework/App/FeedImporterInterface.php +++ b/lib/internal/Magento/Framework/App/FeedImporterInterface.php @@ -9,7 +9,9 @@ interface FeedImporterInterface { /** - * @throws \Magento\Framework\Exception\FeedImporterException + * Returns FeedInterface object from a custom array + * + * @throws \Magento\Framework\Exception\RuntimeException * @param array $data * @param string $format * @return FeedInterface diff --git a/lib/internal/Magento/Framework/Exception/FeedImporterException.php b/lib/internal/Magento/Framework/Exception/FeedImporterException.php deleted file mode 100644 index e6faec0a60270..0000000000000 --- a/lib/internal/Magento/Framework/Exception/FeedImporterException.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Thu, 27 Jul 2017 14:23:10 +0200 Subject: [PATCH 010/236] Zend Feed refactorging WIP --- .../App/{Feed/Importer.php => FeedFactory.php} | 15 +++------------ ...rterInterface.php => FeedFactoryInterface.php} | 7 +++++-- .../Framework/App/FeedFormatsInterface.php | 11 +++++++++++ 3 files changed, 19 insertions(+), 14 deletions(-) rename lib/internal/Magento/Framework/App/{Feed/Importer.php => FeedFactory.php} (74%) rename lib/internal/Magento/Framework/App/{FeedImporterInterface.php => FeedFactoryInterface.php} (71%) create mode 100644 lib/internal/Magento/Framework/App/FeedFormatsInterface.php diff --git a/lib/internal/Magento/Framework/App/Feed/Importer.php b/lib/internal/Magento/Framework/App/FeedFactory.php similarity index 74% rename from lib/internal/Magento/Framework/App/Feed/Importer.php rename to lib/internal/Magento/Framework/App/FeedFactory.php index e0e1ba13ab1e6..978462e4abdc3 100644 --- a/lib/internal/Magento/Framework/App/Feed/Importer.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Framework\App\Feed; +namespace Magento\Framework\App; use Magento\Framework\App\FeedFactory; use Psr\Log\LoggerInterface; @@ -11,18 +11,13 @@ /** * Feed importer */ -class Importer implements \Magento\Framework\App\FeedImporterInterface +class FeedFactory implements \Magento\Framework\App\FeedImporterInterface { /** * @var \Zend_Feed */ private $feedProcessor; - /** - * @var FeedFactory - */ - private $feedFactory; - /** * @var LoggerInterface */ @@ -30,16 +25,13 @@ class Importer implements \Magento\Framework\App\FeedImporterInterface /** * @param \Zend_Feed $feedProcessor - * @param FeedFactory $feedFactory * @param LoggerInterface $logger */ public function __construct( \Zend_Feed $feedProcessor, - FeedFactory $feedFactory, LoggerInterface $logger ) { $this->feedProcessor = $feedProcessor; - $this->feedFactory = $feedFactory; $this->logger = $logger; } @@ -55,8 +47,7 @@ public function importArray(array $data, $format = 'atom') { try { - $feed = $this->feedProcessor->importArray($data, $format); - return $this->feedFactory->create(['feed' => $feed]); + return $this->feedProcessor->importArray($data, $format); } catch (\Zend_Feed_Exception $e) { $this->logger->error($e->getMessage()); throw new \Magento\Framework\Exception\RuntimeException( diff --git a/lib/internal/Magento/Framework/App/FeedImporterInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php similarity index 71% rename from lib/internal/Magento/Framework/App/FeedImporterInterface.php rename to lib/internal/Magento/Framework/App/FeedFactoryInterface.php index cd99b1257b93a..5147fccd638a7 100644 --- a/lib/internal/Magento/Framework/App/FeedImporterInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\App; -interface FeedImporterInterface +interface FeedFactoryInterface { /** @@ -16,5 +16,8 @@ interface FeedImporterInterface * @param string $format * @return FeedInterface */ - public function importArray(array $data, $format = 'atom'); + public function importArray( + array $data, + $format = FeedFormatsInterface::DEFAULT_FORMAT + ); } diff --git a/lib/internal/Magento/Framework/App/FeedFormatsInterface.php b/lib/internal/Magento/Framework/App/FeedFormatsInterface.php new file mode 100644 index 0000000000000..e7610e09f5684 --- /dev/null +++ b/lib/internal/Magento/Framework/App/FeedFormatsInterface.php @@ -0,0 +1,11 @@ + Date: Mon, 7 Aug 2017 15:43:26 -0700 Subject: [PATCH 011/236] Implementing code review requested changes --- app/code/Magento/Rss/Model/Rss.php | 22 ++++++++++++------- .../Magento/Rss/Test/Unit/Model/RssTest.php | 15 +++++++------ app/etc/di.xml | 2 +- lib/internal/Magento/Framework/App/Feed.php | 15 +++++++++---- .../Magento/Framework/App/FeedFactory.php | 7 +++--- .../Framework/App/FeedFormatsInterface.php | 2 +- .../Magento/Framework/App/FeedInterface.php | 4 +++- .../App/FeedOutputFormatsInterface.php | 11 ++++++++++ 8 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 lib/internal/Magento/Framework/App/FeedOutputFormatsInterface.php diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index d90b5accbbdf9..85cf459bdc75f 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -8,7 +8,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\App\FeedImporterInterface; +use Magento\Framework\App\FeedFactoryInterface; class Rss { @@ -23,9 +23,9 @@ class Rss protected $cache; /** - * @var \Magento\Framework\App\FeedImporterInterface + * @var \Magento\Framework\App\FeedFactoryInterface */ - private $feedImporter; + private $feedFactory; /** * @var SerializerInterface @@ -37,16 +37,16 @@ class Rss * * @param \Magento\Framework\App\CacheInterface $cache * @param SerializerInterface|null $serializer - * @param FeedImporterInterface|null $feedImporter + * @param FeedFactoryInterface|null $feedFactory */ public function __construct( \Magento\Framework\App\CacheInterface $cache, SerializerInterface $serializer = null, - FeedImporterInterface $feedImporter = null + FeedFactoryInterface $feedFactory = null ) { $this->cache = $cache; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); - $this->feedImporter = $feedImporter ?: ObjectManager::getInstance()->get(FeedImporterInterface::class); + $this->feedFactory = $feedFactory ?: ObjectManager::getInstance()->get(FeedFactoryInterface::class); } /** @@ -95,7 +95,13 @@ public function setDataProvider(DataProviderInterface $dataProvider) */ public function createRssXml() { - $rssFeed = $this->feedImporter->importArray($this->getFeeds(), 'rss'); - return $rssFeed->asXML(); + $feed = $this->feedFactory->importArray( + $this->getFeeds(), + \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT + ); + + return $feed->getFormatedContentAs( + \Magento\Framework\App\FeedOutputFormatsInterface::DEFAULT_FORMAT + ); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index f84ded89cdfce..68d15a20a7428 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -65,9 +65,9 @@ class RssTest extends \PHPUnit_Framework_TestCase private $cacheMock; /** - * @var \Magento\Framework\App\FeedImporterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\FeedFactoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $feedImporterMock; + private $feedFactoryMock; /** * @var \Magento\Framework\App\FeedInterface|\PHPUnit_Framework_MockObject_MockObject @@ -83,7 +83,7 @@ protected function setUp() { $this->cacheMock = $this->getMock(\Magento\Framework\App\CacheInterface::class); $this->serializerMock = $this->getMock(SerializerInterface::class); - $this->feedImporterMock = $this->getMock(\Magento\Framework\App\FeedImporterInterface::class); + $this->feedFactoryMock = $this->getMock(\Magento\Framework\App\FeedFactoryInterface::class); $this->feedMock = $this->getMock(\Magento\Framework\App\FeedInterface::class); $this->objectManagerHelper = new ObjectManagerHelper($this); @@ -91,7 +91,7 @@ protected function setUp() \Magento\Rss\Model\Rss::class, [ 'cache' => $this->cacheMock, - 'feedImporter' => $this->feedImporterMock, + 'feedFactory' => $this->feedFactoryMock, 'serializer' => $this->serializerMock ] ); @@ -152,12 +152,13 @@ public function testCreateRssXml() $dataProvider->expects($this->any())->method('getRssData')->will($this->returnValue($this->feedData)); $this->feedMock->expects($this->once()) - ->method('asXml') + ->method('getFormatedContentAs') + ->with(\Magento\Framework\App\FeedOutputFormatsInterface::DEFAULT_FORMAT) ->will($this->returnValue($this->feedXml)); - $this->feedImporterMock->expects($this->once()) + $this->feedFactoryMock->expects($this->once()) ->method('importArray') - ->with($this->feedData) + ->with($this->feedData, \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT) ->will($this->returnValue($this->feedMock)); $this->rss->setDataProvider($dataProvider); diff --git a/app/etc/di.xml b/app/etc/di.xml index 3643588dbbf25..ffdd76bab1bf6 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -171,7 +171,7 @@ - + diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 1a8fe94db4b1b..018b7c295d76d 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -8,7 +8,7 @@ class Feed implements \Magento\Framework\App\FeedInterface { /** - * @var \Magento\Framework\App\FeedImporterInterface + * @var \Zend_Feed_Abstract */ private $feed; @@ -25,8 +25,15 @@ public function __construct(\Zend_Feed_Abstract $feed) * * @return string */ - public function asXml() - { - return $this->feed->saveXml(); + public function getFormatedContentAs( + $format = FeedOutputFormatsInterface::DEFAULT_FORMAT + ) { + if ($format === FeedOutputFormatsInterface::DEFAULT_FORMAT) { + return $this->feed->saveXml(); + } + throw new \Magento\Framework\Exception\RuntimeException( + __('Given feed format is not supported'), + $e + ); } } diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 978462e4abdc3..f198b4ad90f0f 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -9,9 +9,9 @@ use Psr\Log\LoggerInterface; /** - * Feed importer + * Feed factory */ -class FeedFactory implements \Magento\Framework\App\FeedImporterInterface +class FeedFactory implements \Magento\Framework\App\FeedFactoryInterface { /** * @var \Zend_Feed @@ -43,9 +43,8 @@ public function __construct( * @param string $format * @return \Magento\Framework\App\FeedInterface */ - public function importArray(array $data, $format = 'atom') + public function importArray(array $data, $format = FeedFormatsInterface::DEFAULT_FORMAT) { - try { return $this->feedProcessor->importArray($data, $format); } catch (\Zend_Feed_Exception $e) { diff --git a/lib/internal/Magento/Framework/App/FeedFormatsInterface.php b/lib/internal/Magento/Framework/App/FeedFormatsInterface.php index e7610e09f5684..027a89f7a47d2 100644 --- a/lib/internal/Magento/Framework/App/FeedFormatsInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFormatsInterface.php @@ -7,5 +7,5 @@ interface FeedFormatsInterface { - const DEFAULT_FORMAT = 'atom'; + const DEFAULT_FORMAT = 'rss'; } \ No newline at end of file diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index 2c3c3ce431920..b8d792f4644d4 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -10,5 +10,7 @@ interface FeedInterface /** * @return string */ - public function asXml(); + public function getFormatedContentAs( + $format = FeedOutputFormatsInterface::DEFAULT_FORMAT + ); } diff --git a/lib/internal/Magento/Framework/App/FeedOutputFormatsInterface.php b/lib/internal/Magento/Framework/App/FeedOutputFormatsInterface.php new file mode 100644 index 0000000000000..e33348a88d551 --- /dev/null +++ b/lib/internal/Magento/Framework/App/FeedOutputFormatsInterface.php @@ -0,0 +1,11 @@ + Date: Mon, 4 Sep 2017 16:49:48 +0200 Subject: [PATCH 012/236] Zend Feed refactoring WIP --- lib/internal/Magento/Framework/App/Feed.php | 6 ++++-- lib/internal/Magento/Framework/App/FeedFactory.php | 13 ++++++++----- .../Magento/Framework/App/FeedFactoryInterface.php | 6 ++++-- .../Magento/Framework/App/FeedFormatsInterface.php | 11 ----------- .../Magento/Framework/App/FeedInterface.php | 4 +++- .../Framework/App/FeedOutputFormatsInterface.php | 11 ----------- 6 files changed, 19 insertions(+), 32 deletions(-) delete mode 100644 lib/internal/Magento/Framework/App/FeedFormatsInterface.php delete mode 100644 lib/internal/Magento/Framework/App/FeedOutputFormatsInterface.php diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 018b7c295d76d..8df5b63a8a461 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\App; +use \Magento\Framework\App\FeedInterface; + class Feed implements \Magento\Framework\App\FeedInterface { /** @@ -26,9 +28,9 @@ public function __construct(\Zend_Feed_Abstract $feed) * @return string */ public function getFormatedContentAs( - $format = FeedOutputFormatsInterface::DEFAULT_FORMAT + $format = FeedInterface::DEFAULT_FORMAT ) { - if ($format === FeedOutputFormatsInterface::DEFAULT_FORMAT) { + if ($format === FeedInterface::DEFAULT_FORMAT) { return $this->feed->saveXml(); } throw new \Magento\Framework\Exception\RuntimeException( diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index f198b4ad90f0f..6ee3775b61687 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -6,12 +6,13 @@ namespace Magento\Framework\App; use Magento\Framework\App\FeedFactory; +use Magento\Framework\App\FeedFactoryInterface; use Psr\Log\LoggerInterface; /** * Feed factory */ -class FeedFactory implements \Magento\Framework\App\FeedFactoryInterface +class FeedFactory implements FeedFactoryInterface { /** * @var \Zend_Feed @@ -38,18 +39,20 @@ public function __construct( /** * Get a new \Magento\Framework\App\Feed object from a custom array * - * @throws \Magento\Framework\Exception\RuntimeException + * @throws \Magento\Framework\Exception\InputException * @param array $data * @param string $format * @return \Magento\Framework\App\FeedInterface */ - public function importArray(array $data, $format = FeedFormatsInterface::DEFAULT_FORMAT) - { + public function create( + array $data, + $format = FeedFactoryInterface::DEFAULT_FORMAT + ) { try { return $this->feedProcessor->importArray($data, $format); } catch (\Zend_Feed_Exception $e) { $this->logger->error($e->getMessage()); - throw new \Magento\Framework\Exception\RuntimeException( + throw new \Magento\Framework\Exception\InputException( __('There has been an error with import'), $e ); diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 5147fccd638a7..2bc9dd735bdcb 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -8,6 +8,8 @@ interface FeedFactoryInterface { + const DEFAULT_FORMAT = 'rss'; + /** * Returns FeedInterface object from a custom array * @@ -16,8 +18,8 @@ interface FeedFactoryInterface * @param string $format * @return FeedInterface */ - public function importArray( + public function create( array $data, - $format = FeedFormatsInterface::DEFAULT_FORMAT + $format = self::DEFAULT_FORMAT ); } diff --git a/lib/internal/Magento/Framework/App/FeedFormatsInterface.php b/lib/internal/Magento/Framework/App/FeedFormatsInterface.php deleted file mode 100644 index 027a89f7a47d2..0000000000000 --- a/lib/internal/Magento/Framework/App/FeedFormatsInterface.php +++ /dev/null @@ -1,11 +0,0 @@ - Date: Wed, 13 Sep 2017 17:17:39 +0300 Subject: [PATCH 013/236] magento/magento2#9347: Zend feed refactoring - Coding style fixes per Travis CI static tests results --- app/code/Magento/Rss/Model/Rss.php | 2 +- lib/internal/Magento/Framework/App/FeedFactory.php | 8 +++----- .../Magento/Framework/App/FeedFactoryInterface.php | 9 ++++----- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index e527319e53990..cff3daa83c209 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -102,7 +102,7 @@ public function setDataProvider(DataProviderInterface $dataProvider) public function createRssXml() { $feed = $this->feedFactory->importArray( - $this->getFeeds(), + $this->getFeeds(), \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT ); diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 6ee3775b61687..c260b4d057fce 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\App; -use Magento\Framework\App\FeedFactory; -use Magento\Framework\App\FeedFactoryInterface; use Psr\Log\LoggerInterface; /** @@ -40,12 +38,12 @@ public function __construct( * Get a new \Magento\Framework\App\Feed object from a custom array * * @throws \Magento\Framework\Exception\InputException - * @param array $data - * @param string $format + * @param array $data + * @param string $format * @return \Magento\Framework\App\FeedInterface */ public function create( - array $data, + array $data, $format = FeedFactoryInterface::DEFAULT_FORMAT ) { try { diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 2bc9dd735bdcb..f7f1066b5d50f 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -7,19 +7,18 @@ interface FeedFactoryInterface { - const DEFAULT_FORMAT = 'rss'; /** * Returns FeedInterface object from a custom array - * + * * @throws \Magento\Framework\Exception\RuntimeException - * @param array $data - * @param string $format + * @param array $data + * @param string $format * @return FeedInterface */ public function create( - array $data, + array $data, $format = self::DEFAULT_FORMAT ); } From 0b51e8c67c9285df1de8c70e23ae109cc8da98f9 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov Date: Wed, 13 Sep 2017 17:26:28 +0300 Subject: [PATCH 014/236] magento/magento2#9347: Zend feed refactoring - Minor fixes per Travis CI unit tests results --- app/code/Magento/Rss/Model/Rss.php | 6 +++--- app/code/Magento/Rss/Test/Unit/Model/RssTest.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index cff3daa83c209..6d9754f12f0f6 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -101,13 +101,13 @@ public function setDataProvider(DataProviderInterface $dataProvider) */ public function createRssXml() { - $feed = $this->feedFactory->importArray( + $feed = $this->feedFactory->create( $this->getFeeds(), - \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT + \Magento\Framework\App\FeedFactoryInterface::DEFAULT_FORMAT ); return $feed->getFormatedContentAs( - \Magento\Framework\App\FeedOutputFormatsInterface::DEFAULT_FORMAT + \Magento\Framework\App\FeedInterface::DEFAULT_FORMAT ); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 5f8f8007314a4..935ef425c11e3 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -153,12 +153,12 @@ public function testCreateRssXml() $this->feedMock->expects($this->once()) ->method('getFormatedContentAs') - ->with(\Magento\Framework\App\FeedOutputFormatsInterface::DEFAULT_FORMAT) + ->with(\Magento\Framework\App\FeedInterface::DEFAULT_FORMAT) ->will($this->returnValue($this->feedXml)); $this->feedFactoryMock->expects($this->once()) - ->method('importArray') - ->with($this->feedData, \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT) + ->method('create') + ->with($this->feedData, \Magento\Framework\App\FeedFactoryInterface::DEFAULT_FORMAT) ->will($this->returnValue($this->feedMock)); $this->rss->setDataProvider($dataProvider); From 74d431a08e53c0fc7e35bc185434c93a801d68b0 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov Date: Wed, 13 Sep 2017 17:41:47 +0300 Subject: [PATCH 015/236] magento/magento2#9347: Zend feed refactoring - Minor fixes per CR --- app/code/Magento/Rss/Model/Rss.php | 11 +++-------- app/code/Magento/Rss/Test/Unit/Model/RssTest.php | 2 +- lib/internal/Magento/Framework/App/Feed.php | 15 ++++++++------- .../Magento/Framework/App/FeedInterface.php | 5 ++--- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index 6d9754f12f0f6..138cd5f004f83 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\App\FeedFactoryInterface; +use Magento\Framework\App\FeedInterface; /** * Provides functionality to work with RSS feeds @@ -101,13 +102,7 @@ public function setDataProvider(DataProviderInterface $dataProvider) */ public function createRssXml() { - $feed = $this->feedFactory->create( - $this->getFeeds(), - \Magento\Framework\App\FeedFactoryInterface::DEFAULT_FORMAT - ); - - return $feed->getFormatedContentAs( - \Magento\Framework\App\FeedInterface::DEFAULT_FORMAT - ); + $feed = $this->feedFactory->create($this->getFeeds(), FeedFactoryInterface::DEFAULT_FORMAT); + return $feed->getFormattedContentAs(FeedInterface::DEFAULT_FORMAT); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 935ef425c11e3..2102d463ed656 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -152,7 +152,7 @@ public function testCreateRssXml() $dataProvider->expects($this->any())->method('getRssData')->will($this->returnValue($this->feedData)); $this->feedMock->expects($this->once()) - ->method('getFormatedContentAs') + ->method('getFormattedContentAs') ->with(\Magento\Framework\App\FeedInterface::DEFAULT_FORMAT) ->will($this->returnValue($this->feedXml)); diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 8df5b63a8a461..173de90473574 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -5,7 +5,8 @@ */ namespace Magento\Framework\App; -use \Magento\Framework\App\FeedInterface; +use Magento\Framework\Phrase; +use Magento\Framework\Exception\InputException; class Feed implements \Magento\Framework\App\FeedInterface { @@ -25,17 +26,17 @@ public function __construct(\Zend_Feed_Abstract $feed) /** * Get the xml from Zend_Feed_Abstract object * + * @param string $format * @return string + * @throws InputException */ - public function getFormatedContentAs( - $format = FeedInterface::DEFAULT_FORMAT - ) { + public function getFormattedContentAs(string $format = FeedInterface::DEFAULT_FORMAT): string + { if ($format === FeedInterface::DEFAULT_FORMAT) { return $this->feed->saveXml(); } - throw new \Magento\Framework\Exception\RuntimeException( - __('Given feed format is not supported'), - $e + throw new InputException( + new Phrase('Given feed format is not supported') ); } } diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index bfd432d01ad81..4ab132c38bb92 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -10,9 +10,8 @@ interface FeedInterface const DEFAULT_FORMAT = 'xml'; /** + * @param string $format * @return string */ - public function getFormatedContentAs( - $format = self::DEFAULT_FORMAT - ); + public function getFormattedContentAs(string $format = self::DEFAULT_FORMAT): string; } From 7f73d67058718202210f95c76655dfa35f00351d Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Mon, 25 Sep 2017 13:56:21 +0200 Subject: [PATCH 016/236] Zend Feed refactoring WIP --- app/code/Magento/Rss/Model/Rss.php | 9 ++-- app/etc/di.xml | 12 ++++++ lib/internal/Magento/Framework/App/Feed.php | 42 +++++++++---------- .../Magento/Framework/App/FeedFactory.php | 35 ++++++++++++---- .../Framework/App/FeedFactoryInterface.php | 2 +- .../Magento/Framework/App/FeedInterface.php | 2 +- 6 files changed, 65 insertions(+), 37 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index e527319e53990..051417140be89 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -8,6 +8,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\App\FeedInterface; use Magento\Framework\App\FeedFactoryInterface; /** @@ -101,13 +102,13 @@ public function setDataProvider(DataProviderInterface $dataProvider) */ public function createRssXml() { - $feed = $this->feedFactory->importArray( + $feed = $this->feedFactory->create( $this->getFeeds(), - \Magento\Framework\App\FeedFormatsInterface::DEFAULT_FORMAT + FeedFactoryInterface::DEFAULT_FORMAT ); - return $feed->getFormatedContentAs( - \Magento\Framework\App\FeedOutputFormatsInterface::DEFAULT_FORMAT + return $feed->getFormattedContentAs( + FeedInterface::DEFAULT_FORMAT ); } } diff --git a/app/etc/di.xml b/app/etc/di.xml index 0bf625b402469..ba3da12e7b435 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -174,6 +174,11 @@ + + + Magento\Framework\Acl\Builder\Proxy + + @@ -243,6 +248,13 @@ + + + + Magento\Framework\App\Feed + + + Cm\RedisSession\Handler\ConfigInterface diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 8df5b63a8a461..9a4f3235a42f4 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -5,37 +5,35 @@ */ namespace Magento\Framework\App; -use \Magento\Framework\App\FeedInterface; - -class Feed implements \Magento\Framework\App\FeedInterface +/** + * Default XML feed class + */ +class Feed implements FeedInterface { /** - * @var \Zend_Feed_Abstract + * @param Zend_Feed $feed + * @param array $data */ - private $feed; - - /** - * @param \Zend_Feed_Abstract $feed - */ - public function __construct(\Zend_Feed_Abstract $feed) - { - $this->feed = $feed; + public function __construct( + \Zend_Feed $feed, + array $data + ) { + $this->feed = $feed; + $this->data = $data; } /** - * Get the xml from Zend_Feed_Abstract object - * + * Returns the formatted feed content + * * @return string */ public function getFormatedContentAs( - $format = FeedInterface::DEFAULT_FORMAT + $format = self::DEFAULT_FORMAT ) { - if ($format === FeedInterface::DEFAULT_FORMAT) { - return $this->feed->saveXml(); - } - throw new \Magento\Framework\Exception\RuntimeException( - __('Given feed format is not supported'), - $e + $feed = $this->feed::importArray( + $this->data, + FeedFactoryInterface::DEFAULT_FORMAT ); + return $this->feed->saveXml(); } -} +} \ No newline at end of file diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 6ee3775b61687..08296ba84cf61 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -5,8 +5,8 @@ */ namespace Magento\Framework\App; -use Magento\Framework\App\FeedFactory; use Magento\Framework\App\FeedFactoryInterface; +use Magento\Framework\ObjectManagerInterface; use Psr\Log\LoggerInterface; /** @@ -15,7 +15,7 @@ class FeedFactory implements FeedFactoryInterface { /** - * @var \Zend_Feed + * @var FeedProcessorInterface */ private $feedProcessor; @@ -25,19 +25,27 @@ class FeedFactory implements FeedFactoryInterface private $logger; /** - * @param \Zend_Feed $feedProcessor + * @var ObjectManagerInterface + */ + protected $objectManager; + + /** + * @param ObjectManagerInterface $objectManger * @param LoggerInterface $logger + * @param array $formats */ public function __construct( - \Zend_Feed $feedProcessor, - LoggerInterface $logger + ObjectManagerInterface $objectManger, + LoggerInterface $logger, + array $formats ) { - $this->feedProcessor = $feedProcessor; + $this->objectManager = $objectManger; $this->logger = $logger; + $this->formats = $formats; } /** - * Get a new \Magento\Framework\App\Feed object from a custom array + * Get a new \Magento\Framework\App\FeedInterface object from a custom array * * @throws \Magento\Framework\Exception\InputException * @param array $data @@ -48,9 +56,18 @@ public function create( array $data, $format = FeedFactoryInterface::DEFAULT_FORMAT ) { + if (!isset($this->formats[$format])) { + throw new \Magento\Framework\Exception\InputException( + __('The format is not supported'), + $e + ); + } try { - return $this->feedProcessor->importArray($data, $format); - } catch (\Zend_Feed_Exception $e) { + return $this->objectManager->create( + $this->formats[$format], + $data + ); + } catch (\Exception $e) { $this->logger->error($e->getMessage()); throw new \Magento\Framework\Exception\InputException( __('There has been an error with import'), diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 2bc9dd735bdcb..59608787fa01e 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -13,7 +13,7 @@ interface FeedFactoryInterface /** * Returns FeedInterface object from a custom array * - * @throws \Magento\Framework\Exception\RuntimeException + * @throws \Magento\Framework\Exception\InputException * @param array $data * @param string $format * @return FeedInterface diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index bfd432d01ad81..a856fba21e1a8 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -12,7 +12,7 @@ interface FeedInterface /** * @return string */ - public function getFormatedContentAs( + public function getFormattedContentAs( $format = self::DEFAULT_FORMAT ); } From 64610d359a54c57d04e73d391a020a297aeeac4f Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Mon, 25 Sep 2017 14:03:53 +0200 Subject: [PATCH 017/236] Removed the wrong lines commited --- app/etc/di.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/etc/di.xml b/app/etc/di.xml index ba3da12e7b435..03cf3294991ae 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -174,11 +174,6 @@ - - - Magento\Framework\Acl\Builder\Proxy - - From 32e2e4de5921ecb7b13722955298e886ec2b2d59 Mon Sep 17 00:00:00 2001 From: Lorenzo Stramaccia Date: Wed, 11 Oct 2017 12:34:46 +0200 Subject: [PATCH 018/236] Fix meta title property --- lib/internal/Magento/Framework/View/Page/Config.php | 1 + .../Magento/Framework/View/Page/Config/Renderer.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index a5bbefb33008e..f12e2b56a60ac 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -117,6 +117,7 @@ class Config 'description' => null, 'keywords' => null, 'robots' => null, + 'title' => null, ]; /** diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index 9563cbfbcc532..a26e341f16dd7 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -136,6 +136,12 @@ public function renderMetadata() protected function processMetadataContent($name, $content) { $method = 'get' . $this->string->upperCaseWords($name, '_', ''); + if($name === 'title') { + if (!$content) { + $content = $this->escaper->escapeHtml($this->pageConfig->$method()->get()); + } + return $content; + } if (method_exists($this->pageConfig, $method)) { $content = $this->pageConfig->$method(); } From b11c7e78b24450ec4d3e73534c40ab427e786cdd Mon Sep 17 00:00:00 2001 From: Lorenzo Stramaccia Date: Thu, 12 Oct 2017 10:17:25 +0200 Subject: [PATCH 019/236] Update unit test --- .../Magento/Framework/View/Test/Unit/Page/ConfigTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index 400e9cf3d1ed7..23d391c7f4f8d 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -13,7 +13,7 @@ use Magento\Framework\View\Page\Config; /** - * @covers Magento\Framework\View\Page\Config + * @covers \Magento\Framework\View\Page\Config * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -139,6 +139,7 @@ public function testMetadata() 'description' => null, 'keywords' => null, 'robots' => null, + 'title' => null, 'name' => 'test_value', 'html_encoded' => '<title><span class="test">Test</span></title>', ]; From 474e43cfb97617355d6d17ec7eb7639789319a7d Mon Sep 17 00:00:00 2001 From: Lorenzo Stramaccia Date: Thu, 12 Oct 2017 10:33:09 +0200 Subject: [PATCH 020/236] Fix coding standards --- lib/internal/Magento/Framework/View/Page/Config/Renderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index a26e341f16dd7..93c8c5c338627 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -136,7 +136,7 @@ public function renderMetadata() protected function processMetadataContent($name, $content) { $method = 'get' . $this->string->upperCaseWords($name, '_', ''); - if($name === 'title') { + if ($name === 'title') { if (!$content) { $content = $this->escaper->escapeHtml($this->pageConfig->$method()->get()); } From d2d397a7acfe29db9bc2d55213eea2f01f393567 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 16 Nov 2017 14:18:55 +0100 Subject: [PATCH 021/236] Fixing stuff as requested --- lib/internal/Magento/Framework/App/Feed.php | 6 +++--- lib/internal/Magento/Framework/App/FeedFactory.php | 14 +++++++++++--- .../Magento/Framework/App/FeedFactoryInterface.php | 4 ++-- .../Magento/Framework/App/FeedInterface.php | 4 ++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 9a4f3235a42f4..c9762c31157d7 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -27,12 +27,12 @@ public function __construct( * * @return string */ - public function getFormatedContentAs( - $format = self::DEFAULT_FORMAT + public function getFormattedContentAs( + $format = self::FORMAT_XML ) { $feed = $this->feed::importArray( $this->data, - FeedFactoryInterface::DEFAULT_FORMAT + FeedFactoryInterface::FORMAT_RSS ); return $this->feed->saveXml(); } diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 08296ba84cf61..b4f3b56d9a8e0 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -27,7 +27,7 @@ class FeedFactory implements FeedFactoryInterface /** * @var ObjectManagerInterface */ - protected $objectManager; + private $objectManager; /** * @param ObjectManagerInterface $objectManger @@ -54,7 +54,7 @@ public function __construct( */ public function create( array $data, - $format = FeedFactoryInterface::DEFAULT_FORMAT + $format = FeedFactoryInterface::FORMAT_RSS ) { if (!isset($this->formats[$format])) { throw new \Magento\Framework\Exception\InputException( @@ -62,6 +62,14 @@ public function create( $e ); } + + if (!is_subclass_of($this->formats[$format], '\Magento\Framework\App\FeedInterface')) { + throw new \Magento\Framework\Exception\InputException( + __('Wrong format handler type'), + $e + ); + } + try { return $this->objectManager->create( $this->formats[$format], @@ -69,7 +77,7 @@ public function create( ); } catch (\Exception $e) { $this->logger->error($e->getMessage()); - throw new \Magento\Framework\Exception\InputException( + throw new \Magento\Framework\Exception\RuntimeException( __('There has been an error with import'), $e ); diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 59608787fa01e..0d51040b4088c 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -8,7 +8,7 @@ interface FeedFactoryInterface { - const DEFAULT_FORMAT = 'rss'; + const FORMAT_RSS = 'rss'; /** * Returns FeedInterface object from a custom array @@ -20,6 +20,6 @@ interface FeedFactoryInterface */ public function create( array $data, - $format = self::DEFAULT_FORMAT + $format = self::FORMAT_RSS ); } diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index a856fba21e1a8..29a055841ff0e 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -7,12 +7,12 @@ interface FeedInterface { - const DEFAULT_FORMAT = 'xml'; + const FORMAT_XML = 'xml'; /** * @return string */ public function getFormattedContentAs( - $format = self::DEFAULT_FORMAT + $format = self::FORMAT_XML ); } From 07702273cc86b6aebea9fc37eb37cb09932968a7 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 23 Nov 2017 17:32:16 +0100 Subject: [PATCH 022/236] Some code style fixes --- lib/internal/Magento/Framework/App/Feed.php | 2 ++ lib/internal/Magento/Framework/App/FeedFactory.php | 9 ++------- .../Magento/Framework/App/FeedFactoryInterface.php | 8 +++++++- lib/internal/Magento/Framework/App/FeedInterface.php | 8 ++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index c9762c31157d7..1e7dc89e932e3 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -24,6 +24,8 @@ public function __construct( /** * Returns the formatted feed content + * + * @param string $format * * @return string */ diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index b4f3b56d9a8e0..50d03ffd65cd3 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -45,12 +45,7 @@ public function __construct( } /** - * Get a new \Magento\Framework\App\FeedInterface object from a custom array - * - * @throws \Magento\Framework\Exception\InputException - * @param array $data - * @param string $format - * @return \Magento\Framework\App\FeedInterface + * {@inheritdoc} */ public function create( array $data, @@ -63,7 +58,7 @@ public function create( ); } - if (!is_subclass_of($this->formats[$format], '\Magento\Framework\App\FeedInterface')) { + if (!is_subclass_of($this->formats[$format], \Magento\Framework\App\FeedInterface::class)) { throw new \Magento\Framework\Exception\InputException( __('Wrong format handler type'), $e diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 0d51040b4088c..5b534c547afe3 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -5,15 +5,21 @@ */ namespace Magento\Framework\App; +/** + * Feed factory interface + */ interface FeedFactoryInterface { - + /** + * RSS feed input format + */ const FORMAT_RSS = 'rss'; /** * Returns FeedInterface object from a custom array * * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\RuntimeException * @param array $data * @param string $format * @return FeedInterface diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index 29a055841ff0e..24858767db7c4 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -5,11 +5,19 @@ */ namespace Magento\Framework\App; +/** + * Feed interface + */ interface FeedInterface { + /** + * XML feed output format + */ const FORMAT_XML = 'xml'; /** + * @param string $format + * * @return string */ public function getFormattedContentAs( From 735c840c731bf9b26e2da5723c3802fb158e54c8 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 23 Nov 2017 17:33:46 +0100 Subject: [PATCH 023/236] RSS model fix --- app/code/Magento/Rss/Model/Rss.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index 051417140be89..2f0d044eecd4e 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -104,11 +104,11 @@ public function createRssXml() { $feed = $this->feedFactory->create( $this->getFeeds(), - FeedFactoryInterface::DEFAULT_FORMAT + FeedFactoryInterface::FORMAT_RSS ); return $feed->getFormattedContentAs( - FeedInterface::DEFAULT_FORMAT + FeedInterface::FORMAT_RSS ); } } From 7e17d2077a75df92afb39815bc91b0dd42a70b85 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Fri, 24 Nov 2017 12:33:30 +0100 Subject: [PATCH 024/236] Unit test fix --- app/code/Magento/Rss/Test/Unit/Model/RssTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 2102d463ed656..8a5d2efd756a8 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -153,12 +153,12 @@ public function testCreateRssXml() $this->feedMock->expects($this->once()) ->method('getFormattedContentAs') - ->with(\Magento\Framework\App\FeedInterface::DEFAULT_FORMAT) + ->with(\Magento\Framework\App\FeedInterface::FORMAT_XML) ->will($this->returnValue($this->feedXml)); $this->feedFactoryMock->expects($this->once()) ->method('create') - ->with($this->feedData, \Magento\Framework\App\FeedFactoryInterface::DEFAULT_FORMAT) + ->with($this->feedData, \Magento\Framework\App\FeedFactoryInterface::FORMAT_RSS) ->will($this->returnValue($this->feedMock)); $this->rss->setDataProvider($dataProvider); From 46c51baf04020995a98c0c6f70e64eca6b14e1fe Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Fri, 24 Nov 2017 12:34:09 +0100 Subject: [PATCH 025/236] RSS model fix --- app/code/Magento/Rss/Model/Rss.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index 2f0d044eecd4e..eb83ddd0ee4dc 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -108,7 +108,7 @@ public function createRssXml() ); return $feed->getFormattedContentAs( - FeedInterface::FORMAT_RSS + FeedInterface::FORMAT_XML ); } } From 3a3683f0a7244078609cd0e512ce6743af8c322b Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Fri, 22 Dec 2017 16:49:16 +0100 Subject: [PATCH 026/236] Code style fixes --- app/code/Magento/Rss/Model/Rss.php | 2 +- lib/internal/Magento/Framework/App/Feed.php | 10 +++++----- lib/internal/Magento/Framework/App/FeedFactory.php | 4 ++-- .../Magento/Framework/App/FeedFactoryInterface.php | 4 ++-- lib/internal/Magento/Framework/App/FeedInterface.php | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index eb83ddd0ee4dc..200ffb967ad1a 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -103,7 +103,7 @@ public function setDataProvider(DataProviderInterface $dataProvider) public function createRssXml() { $feed = $this->feedFactory->create( - $this->getFeeds(), + $this->getFeeds(), FeedFactoryInterface::FORMAT_RSS ); diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 1e7dc89e932e3..63d56260bf34a 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -18,24 +18,24 @@ public function __construct( \Zend_Feed $feed, array $data ) { - $this->feed = $feed; - $this->data = $data; + $this->feed = $feed; + $this->data = $data; } /** * Returns the formatted feed content * * @param string $format - * + * * @return string */ public function getFormattedContentAs( $format = self::FORMAT_XML ) { $feed = $this->feed::importArray( - $this->data, + $this->data, FeedFactoryInterface::FORMAT_RSS ); return $this->feed->saveXml(); } -} \ No newline at end of file +} diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 50d03ffd65cd3..57eaca03245fc 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -48,7 +48,7 @@ public function __construct( * {@inheritdoc} */ public function create( - array $data, + array $data, $format = FeedFactoryInterface::FORMAT_RSS ) { if (!isset($this->formats[$format])) { @@ -62,7 +62,7 @@ public function create( throw new \Magento\Framework\Exception\InputException( __('Wrong format handler type'), $e - ); + ); } try { diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 5b534c547afe3..b35e3379f9f19 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -17,7 +17,7 @@ interface FeedFactoryInterface /** * Returns FeedInterface object from a custom array - * + * * @throws \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\RuntimeException * @param array $data @@ -25,7 +25,7 @@ interface FeedFactoryInterface * @return FeedInterface */ public function create( - array $data, + array $data, $format = self::FORMAT_RSS ); } diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index 24858767db7c4..661f8a65e70ac 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -10,14 +10,14 @@ */ interface FeedInterface { - /** + /** * XML feed output format */ const FORMAT_XML = 'xml'; /** * @param string $format - * + * * @return string */ public function getFormattedContentAs( From 6311d5cf7444436245e4326051a893f290e70bcd Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Fri, 22 Dec 2017 17:50:27 +0100 Subject: [PATCH 027/236] Code fixes --- lib/internal/Magento/Framework/App/Feed.php | 2 +- lib/internal/Magento/Framework/App/FeedFactory.php | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 63d56260bf34a..01634be6bfaf4 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -36,6 +36,6 @@ public function getFormattedContentAs( $this->data, FeedFactoryInterface::FORMAT_RSS ); - return $this->feed->saveXml(); + return $feed->saveXml(); } } diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 57eaca03245fc..129341824f58d 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -14,11 +14,6 @@ */ class FeedFactory implements FeedFactoryInterface { - /** - * @var FeedProcessorInterface - */ - private $feedProcessor; - /** * @var LoggerInterface */ @@ -53,14 +48,14 @@ public function create( ) { if (!isset($this->formats[$format])) { throw new \Magento\Framework\Exception\InputException( - __('The format is not supported'), + new \Magento\Framework\Phrase('The format is not supported'), $e ); } if (!is_subclass_of($this->formats[$format], \Magento\Framework\App\FeedInterface::class)) { throw new \Magento\Framework\Exception\InputException( - __('Wrong format handler type'), + new \Magento\Framework\Phrase('Wrong format handler type'), $e ); } @@ -73,7 +68,7 @@ public function create( } catch (\Exception $e) { $this->logger->error($e->getMessage()); throw new \Magento\Framework\Exception\RuntimeException( - __('There has been an error with import'), + new \Magento\Framework\Phrase('There has been an error with import'), $e ); } From 4462eaf918f1e8d7362c19cba6bb6db2175efc64 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Fri, 22 Dec 2017 17:57:08 +0100 Subject: [PATCH 028/236] Added some missing properties and renamed stuff --- lib/internal/Magento/Framework/App/Feed.php | 18 ++++++++++++++---- .../Magento/Framework/App/FeedFactory.php | 9 +++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 01634be6bfaf4..a76acc2486eb0 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -11,14 +11,24 @@ class Feed implements FeedInterface { /** - * @param Zend_Feed $feed + * @var \Zend_Feed + */ + private $feedProcessor; + + /** + * @var array + */ + private $data; + + /** + * @param Zend_Feed $feedProcessor * @param array $data */ public function __construct( - \Zend_Feed $feed, + \Zend_Feed $feedProcessor, array $data ) { - $this->feed = $feed; + $this->feedProcessor = $feedProcessor; $this->data = $data; } @@ -32,7 +42,7 @@ public function __construct( public function getFormattedContentAs( $format = self::FORMAT_XML ) { - $feed = $this->feed::importArray( + $feed = $this->feedProcessor::importArray( $this->data, FeedFactoryInterface::FORMAT_RSS ); diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 129341824f58d..87bd87fdc84c2 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -14,15 +14,20 @@ */ class FeedFactory implements FeedFactoryInterface { + /** + * @var ObjectManagerInterface + */ + private $objectManager; + /** * @var LoggerInterface */ private $logger; /** - * @var ObjectManagerInterface + * @var array */ - private $objectManager; + private $formats; /** * @param ObjectManagerInterface $objectManger From 29431e958855ef19cc5289f8c86f39e2bc30f0a5 Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Sun, 18 Feb 2018 16:08:58 +0100 Subject: [PATCH 029/236] Category collection now use getStoreId and not directly the store manager --- .../ResourceModel/Category/Collection.php | 2 +- .../Category/Collection/UrlRewriteTest.php | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php index 573914a13f6e5..46bb74513b59c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php @@ -371,7 +371,7 @@ public function joinUrlRewrite() ['request_path'], sprintf( '{{table}}.is_autogenerated = 1 AND {{table}}.store_id = %d AND {{table}}.entity_type = \'%s\'', - $this->_storeManager->getStore()->getId(), + $this->getStoreId(), CategoryUrlRewriteGenerator::ENTITY_TYPE ), 'left' diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php new file mode 100644 index 0000000000000..5949b2951de03 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php @@ -0,0 +1,40 @@ +_model = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Category\Collection::class + )->disableOriginalConstructor() + ->setMethodsExcept(['joinUrlRewrite', 'setStoreId', 'getStoreId']) + ->getMock(); + } + + + public function testStoreIdUsedByUrlRewrite() + { + $this->_model->expects($this->once()) + ->method('joinTable') + ->with( + $this->anything(), + $this->anything(), + $this->anything(), + $this->equalTo('{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''), + $this->anything() + ); + $this->_model->setStoreId(100); + $this->_model->joinUrlRewrite(); + } +} From 47c8d54a84770286e9912b83cf11805d29bf40ea Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Sun, 18 Feb 2018 17:06:42 +0100 Subject: [PATCH 030/236] code style fixes --- .../ResourceModel/Category/Collection/UrlRewriteTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php index 5949b2951de03..ea8cb51bf5e5e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php @@ -22,16 +22,16 @@ protected function setUp() ->getMock(); } - public function testStoreIdUsedByUrlRewrite() { + $cond = '{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''; $this->_model->expects($this->once()) ->method('joinTable') ->with( $this->anything(), $this->anything(), $this->anything(), - $this->equalTo('{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''), + $this->equalTo($cond), $this->anything() ); $this->_model->setStoreId(100); From 2a014892b5d19c04f5462d2f00c3c7f5b557b0c2 Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Sun, 18 Feb 2018 20:05:40 +0100 Subject: [PATCH 031/236] fixes to code style --- .../Category/Collection/UrlRewriteTest.php | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php index ea8cb51bf5e5e..7dec04fceaef6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php @@ -15,9 +15,8 @@ class UrlRewriteTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->_model = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Category\Collection::class - )->disableOriginalConstructor() + $this->_model = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category\Collection::class) + ->disableOriginalConstructor() ->setMethodsExcept(['joinUrlRewrite', 'setStoreId', 'getStoreId']) ->getMock(); } @@ -26,14 +25,14 @@ public function testStoreIdUsedByUrlRewrite() { $cond = '{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''; $this->_model->expects($this->once()) - ->method('joinTable') - ->with( - $this->anything(), - $this->anything(), - $this->anything(), - $this->equalTo($cond), - $this->anything() - ); + ->method('joinTable') + ->with( + $this->anything(), + $this->anything(), + $this->anything(), + $this->equalTo($cond), + $this->anything() + ); $this->_model->setStoreId(100); $this->_model->joinUrlRewrite(); } From 2d12dac1672aa4125b1012e454a8b58bb9ee9b73 Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Sun, 18 Mar 2018 23:26:09 +0100 Subject: [PATCH 032/236] add integration test --- .../ResourceModel/Category/CollectionTest.php | 62 +++++++++++++++++++ .../_files/category_multiple_stores.php | 61 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php new file mode 100644 index 0000000000000..7d2d5d8e443a7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php @@ -0,0 +1,62 @@ +collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Category\Collection::class + ); + } + + protected function setDown() { + /* Refresh stores memory cache after store deletion */ + \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->reinitStores(); + } + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php + */ + public function testJoinUrlRewriteOnDefault() + { + $categories = $this->collection->joinUrlRewrite()->addPathFilter('1/2/3'); + $this->assertCount(1, $categories); + /** @var $category \Magento\Catalog\Model\Category */ + $category = $categories->getFirstItem(); + $this->assertStringEndsWith('category.html', $category->getUrl()); + } + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php + */ + public function testJoinUrlRewriteNotOnDefaultStore() + { + $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); + $storeId = $store->load('second_category_store', 'code')->getId(); + $categories = $this->collection->setStoreId($storeId)->joinUrlRewrite()->addPathFilter('1/2/3'); + $this->assertCount(1, $categories); + /** @var $category \Magento\Catalog\Model\Category */ + $category = $categories->getFirstItem(); + $this->assertStringEndsWith('category-3-on-2.html', $category->getUrl()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php new file mode 100644 index 0000000000000..c6590bb6a0f9f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php @@ -0,0 +1,61 @@ +create( + \Magento\Catalog\Model\CategoryFactory::class +); +/** @var \Magento\Catalog\Model\CategoryRepository $repository */ +$repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\CategoryRepository::class +); +/** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ +$storeManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Store\Model\StoreManagerInterface::class +); +/** @var \Magento\Store\Model\Store $store */ +$store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); +if (!$store->load('second_category_store', 'code')->getId()) { + $websiteId = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId(); + $groupId = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getDefaultGroupId(); + $store->setCode( + 'second_category_store' + )->setWebsiteId( + $websiteId + )->setGroupId( + $groupId + )->setName( + 'Fixture Store' + )->setSortOrder( + 10 + )->setIsActive( + 1 + ); + $store->save(); +} + +/* Refresh stores memory cache */ +\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class +)->reinitStores(); + +/** @var \Magento\Catalog\Model\Category $newCategory */ +$newCategory = $factory->create(); +$newCategory + ->setName('Category') + ->setParentId(2) + ->setLevel(2) + ->setPath('1/2/3') + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1); +$repository->save($newCategory); +$currentStoreId = $storeManager->getStore()->getId(); +$storeManager->setCurrentStore($storeManager->getStore($store->getId())); +$newCategory + ->setUrlKey('category-3-on-2'); +$repository->save($newCategory); +$storeManager->setCurrentStore($storeManager->getStore($currentStoreId)); \ No newline at end of file From 8a097fc1a83acd63b702661e12bcb10a00b36630 Mon Sep 17 00:00:00 2001 From: Alessandro Pagnin Date: Sun, 18 Mar 2018 23:47:39 +0100 Subject: [PATCH 033/236] fix to code style --- .../Catalog/Model/ResourceModel/Category/CollectionTest.php | 6 ++++-- .../Model/ResourceModel/_files/category_multiple_stores.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php index 7d2d5d8e443a7..dd1a6eefbe532 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php @@ -23,7 +23,8 @@ protected function setUp() ); } - protected function setDown() { + protected function setDown() + { /* Refresh stores memory cache after store deletion */ \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( \Magento\Store\Model\StoreManagerInterface::class @@ -51,7 +52,8 @@ public function testJoinUrlRewriteOnDefault() */ public function testJoinUrlRewriteNotOnDefaultStore() { - $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); + $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Store\Model\Store::class); $storeId = $store->load('second_category_store', 'code')->getId(); $categories = $this->collection->setStoreId($storeId)->joinUrlRewrite()->addPathFilter('1/2/3'); $this->assertCount(1, $categories); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php index c6590bb6a0f9f..330a682106d89 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php @@ -58,4 +58,4 @@ $newCategory ->setUrlKey('category-3-on-2'); $repository->save($newCategory); -$storeManager->setCurrentStore($storeManager->getStore($currentStoreId)); \ No newline at end of file +$storeManager->setCurrentStore($storeManager->getStore($currentStoreId)); From af3804ac4e846d42593ab73df3535a24d4ff9f8b Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Mon, 26 Mar 2018 16:23:03 +0300 Subject: [PATCH 034/236] magento/magento2#13716 --- .../Category/Collection/UrlRewriteTest.php | 10 +++++----- .../Model/ResourceModel/Category/CollectionTest.php | 2 +- .../ResourceModel/_files/category_multiple_stores.php | 9 +++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php index 7dec04fceaef6..b1f5f17f64a59 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php @@ -11,11 +11,11 @@ class UrlRewriteTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_model; + private $model; protected function setUp() { - $this->_model = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category\Collection::class) + $this->model = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category\Collection::class) ->disableOriginalConstructor() ->setMethodsExcept(['joinUrlRewrite', 'setStoreId', 'getStoreId']) ->getMock(); @@ -24,7 +24,7 @@ protected function setUp() public function testStoreIdUsedByUrlRewrite() { $cond = '{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''; - $this->_model->expects($this->once()) + $this->model->expects($this->once()) ->method('joinTable') ->with( $this->anything(), @@ -33,7 +33,7 @@ public function testStoreIdUsedByUrlRewrite() $this->equalTo($cond), $this->anything() ); - $this->_model->setStoreId(100); - $this->_model->joinUrlRewrite(); + $this->model->setStoreId(100); + $this->model->joinUrlRewrite(); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php index dd1a6eefbe532..7e26cdb921f39 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php @@ -10,7 +10,7 @@ class CollectionTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Catalog\Model\ResourceModel\Category\Collection */ - protected $collection; + private $collection; /** * Sets up the fixture, for example, opens a network connection. diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php index 330a682106d89..90bf630000e72 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php @@ -1,4 +1,9 @@ create( \Magento\Catalog\Model\CategoryFactory::class @@ -20,6 +25,7 @@ $groupId = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( \Magento\Store\Model\StoreManagerInterface::class )->getWebsite()->getDefaultGroupId(); + $store->setCode( 'second_category_store' )->setWebsiteId( @@ -55,7 +61,6 @@ $repository->save($newCategory); $currentStoreId = $storeManager->getStore()->getId(); $storeManager->setCurrentStore($storeManager->getStore($store->getId())); -$newCategory - ->setUrlKey('category-3-on-2'); +$newCategory->setUrlKey('category-3-on-2'); $repository->save($newCategory); $storeManager->setCurrentStore($storeManager->getStore($currentStoreId)); From f5e429d52724795c804498a87884e84619149ea5 Mon Sep 17 00:00:00 2001 From: Bartek Igielski Date: Tue, 27 Mar 2018 16:20:57 +0200 Subject: [PATCH 035/236] Fixed extends and removed unnecessary variables --- .../web/css/source/module/checkout/_tooltip.less | 3 +-- .../Magento/blank/Magento_Sales/web/css/source/_module.less | 2 +- .../frontend/Magento/blank/web/css/source/_extends.less | 2 +- .../blank/web/css/source/components/_modals_extend.less | 5 ++--- .../frontend/Magento/luma/web/css/source/_extends.less | 2 +- .../luma/web/css/source/components/_modals_extend.less | 5 ++--- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less index cf84f34279086..273f626ec03d6 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less @@ -8,7 +8,6 @@ // _____________________________________________ @checkout-tooltip__hover__z-index: @tooltip__z-index; -@checkout-tooltip-breakpoint__screen-m: @modal-popup-breakpoint-screen__m; @checkout-tooltip-icon-arrow__font-size: 10px; @checkout-tooltip-icon-arrow__left: -( @checkout-tooltip-content__padding + @checkout-tooltip-icon-arrow__font-size - @checkout-tooltip-content__border-width); @@ -138,7 +137,7 @@ } } -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @checkout-tooltip-breakpoint__screen-m) { +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .field-tooltip { .field-tooltip-content { &:extend(.abs-checkout-tooltip-content-position-top-mobile all); diff --git a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less index 1ea1e2c483d0b..3847393a2f046 100644 --- a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less @@ -262,7 +262,7 @@ } .toolbar { - &:extend(.abs-add-clearfix-desktop all); + &:extend(.abs-add-clearfix-mobile all); .pages { float: right; diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index d4bede1279602..a36934111eb06 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -1233,7 +1233,7 @@ } } -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = (@screen__m + 1)) { +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .abs-checkout-tooltip-content-position-top-mobile { @abs-checkout-tooltip-content-position-top(); } diff --git a/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less index d324bbeac598f..d76630b5cea47 100644 --- a/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/blank/web/css/source/components/_modals_extend.less @@ -16,7 +16,6 @@ @modal-popup-title__font-size: 26px; @modal-popup-title-mobile__font-size: @font-size__base; -@modal-popup-breakpoint-screen__m: @screen__m; @modal-slide__first__indent-left: 44px; @modal-slide-mobile__background-color: @color-gray-light01; @@ -149,7 +148,7 @@ } } -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @modal-popup-breakpoint-screen__m) { +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .modal-popup { &.modal-slide { .modal-inner-wrap[class] { @@ -180,7 +179,7 @@ // Desktop // _____________________________________________ -.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @modal-popup-breakpoint-screen__m) { +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .modal-popup { &.modal-slide { .modal-footer { diff --git a/app/design/frontend/Magento/luma/web/css/source/_extends.less b/app/design/frontend/Magento/luma/web/css/source/_extends.less index 602ed44d5a44d..760ec9ed861a9 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -1681,7 +1681,7 @@ } } -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = (@screen__m + 1)) { +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .abs-checkout-tooltip-content-position-top-mobile { @abs-checkout-tooltip-content-position-top(); } diff --git a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less index b3165a41964e5..e90d312aa7801 100644 --- a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less @@ -16,7 +16,6 @@ @modal-popup-title__font-size: 26px; @modal-popup-title-mobile__font-size: @font-size__base; -@modal-popup-breakpoint-screen__m: @screen__m; @modal-slide__first__indent-left: 44px; @modal-slide-mobile__background-color: @color-gray-light01; @@ -148,7 +147,7 @@ } } -.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @modal-popup-breakpoint-screen__m) { +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .modal-popup { &.modal-slide { .modal-inner-wrap[class] { @@ -179,7 +178,7 @@ // Desktop // _____________________________________________ -.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @modal-popup-breakpoint-screen__m) { +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .modal-popup { &.modal-slide { .modal-footer { From 9fcc4dacac4c42090a10fe15218a3140f0863fd2 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Thu, 29 Mar 2018 12:15:25 +0300 Subject: [PATCH 036/236] MAGETWO-89540: Static CompilerTest doesn't understand nullable type hint --- .../Framework/Code/Reader/SourceArgumentsReaderTest.php | 2 ++ .../Code/Reader/_files/SourceArgumentsReaderTest.php.sample | 2 ++ .../Magento/Framework/Code/Reader/SourceArgumentsReader.php | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php index d1b22e853ce1d..035033445ca6f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php @@ -44,6 +44,8 @@ public function getConstructorArgumentTypesDataProvider() '\Imported\Name\Space\ClassName\Under\Test', '\Imported\Name\Space\ClassName', '\Some\Testing\Name\Space\Test', + '\Exception', + '\Imported\Name\Space\ClassName', 'array', '' ], diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample index 47c059de2034b..6d9322f8c8971 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample @@ -19,6 +19,8 @@ class AnotherSimpleClass ClassName\Under\Test $itemFive, ClassName $itemSix, Test $itemSeven, + ?\Exception $optionalException, + ?ClassName $optionalWithDefaultValue = null, array $itemEight = [], $itemNine = 'test' ) { diff --git a/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php index 0fb3825d240a1..5aabbf1b466a1 100644 --- a/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php @@ -56,7 +56,7 @@ public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited $source = ' &$argument) { $argument = $this->removeToken($argument, '='); $argument = $this->removeToken($argument, '&'); + if (mb_strpos($argument, '?') === 0) { + $argument = mb_substr($argument, 1); + } $argument = $this->namespaceResolver->resolveNamespace($argument, $availableNamespaces); } unset($argument); From ef4ca85e70465aad5dffbca316fdf555ffac5549 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Thu, 29 Mar 2018 14:43:17 +0300 Subject: [PATCH 037/236] MAGETWO-89540: Static CompilerTest doesn't understand nullable type hint --- .../Code/Reader/SourceArgumentsReaderTest.php | 1 + .../SourceArgumentsReaderTest.php.sample | 1 + .../Code/Reader/SourceArgumentsReader.php | 81 ++++++++----------- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php index 035033445ca6f..103df9f570a2a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/SourceArgumentsReaderTest.php @@ -45,6 +45,7 @@ public function getConstructorArgumentTypesDataProvider() '\Imported\Name\Space\ClassName', '\Some\Testing\Name\Space\Test', '\Exception', + '', '\Imported\Name\Space\ClassName', 'array', '' diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample index 6d9322f8c8971..5efe7837e7a3a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/Reader/_files/SourceArgumentsReaderTest.php.sample @@ -20,6 +20,7 @@ class AnotherSimpleClass ClassName $itemSix, Test $itemSeven, ?\Exception $optionalException, + $defaultString = '$default),value;', ?ClassName $optionalWithDefaultValue = null, array $itemEight = [], $itemNine = 'test' diff --git a/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php index 5aabbf1b466a1..31243f6ad98f9 100644 --- a/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/SourceArgumentsReader.php @@ -37,8 +37,10 @@ public function __construct(NamespaceResolver $namespaceResolver = null) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited = false) - { + public function getConstructorArgumentTypes( + \ReflectionClass $class, + $inherited = false + ) { $output = [null]; if (!$class->getFileName() || false == $class->hasMethod( '__construct' @@ -46,52 +48,37 @@ public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited ) { return $output; } - $reflectionConstructor = $class->getConstructor(); - $fileContent = file($class->getFileName()); - $availableNamespaces = $this->namespaceResolver->getImportedNamespaces($fileContent); - $availableNamespaces[0] = $class->getNamespaceName(); - $constructorStartLine = $reflectionConstructor->getStartLine() - 1; - $constructorEndLine = $reflectionConstructor->getEndLine(); - $fileContent = array_slice($fileContent, $constructorStartLine, $constructorEndLine - $constructorStartLine); - $source = ' $argument]; - } - unset($argument); - $arguments = array_filter($arguments, function ($token) { - $blacklist = [T_VARIABLE, T_WHITESPACE]; - if (isset($token[0]) && in_array($token[0], $blacklist)) { - return false; + //Reading parameters' types. + $params = $class->getConstructor()->getParameters(); + /** @var string[] $types */ + $types = []; + foreach ($params as $param) { + //For the sake of backward compatibility. + $typeName = ''; + if ($param->isArray()) { + //For the sake of backward compatibility. + $typeName = 'array'; + } else { + try { + $paramClass = $param->getClass(); + if ($paramClass) { + $typeName = '\\' .$paramClass->getName(); + } + } catch (\ReflectionException $exception) { + //If there's a problem loading a class then ignore it and + //just return it's name. + $typeName = '\\' .$param->getType()->getName(); + } } - return true; - }); - $arguments = array_map(function ($element) { - return $element[1]; - }, $arguments); - $arguments = array_values($arguments); - $arguments = implode('', $arguments); - if (empty($arguments)) { - return $output; + $types[] = $typeName; } - $arguments = explode(',', $arguments); - foreach ($arguments as $key => &$argument) { - $argument = $this->removeToken($argument, '='); - $argument = $this->removeToken($argument, '&'); - if (mb_strpos($argument, '?') === 0) { - $argument = mb_substr($argument, 1); - } - $argument = $this->namespaceResolver->resolveNamespace($argument, $availableNamespaces); + if (!$types) { + //For the sake of backward compatibility. + $types = [null]; } - unset($argument); - return $arguments; + + return $types; } /** @@ -101,7 +88,7 @@ public function getConstructorArgumentTypes(\ReflectionClass $class, $inherited * @param array $availableNamespaces * @return string * @deprecated 100.2.0 - * @see \Magento\Framework\Code\Reader\NamespaceResolver::resolveNamespace + * @see getConstructorArgumentTypes */ protected function resolveNamespaces($argument, $availableNamespaces) { @@ -114,6 +101,8 @@ protected function resolveNamespaces($argument, $availableNamespaces) * @param string $argument * @param string $token * @return string + * + * @deprecated Not used anymore. */ protected function removeToken($argument, $token) { @@ -130,7 +119,7 @@ protected function removeToken($argument, $token) * @param array $file * @return array * @deprecated 100.2.0 - * @see \Magento\Framework\Code\Reader\NamespaceResolver::getImportedNamespaces + * @see getConstructorArgumentTypes */ protected function getImportedNamespaces(array $file) { From 8220e3ff214d8911ba7dde971ed6856272228ca1 Mon Sep 17 00:00:00 2001 From: Vlad Veselov Date: Tue, 27 Mar 2018 11:35:13 +0300 Subject: [PATCH 038/236] Remove improper unit test it shall not pass code review as object under test is mocked and thus it does not test anything --- .../Category/Collection/UrlRewriteTest.php | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php deleted file mode 100644 index b1f5f17f64a59..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Category/Collection/UrlRewriteTest.php +++ /dev/null @@ -1,39 +0,0 @@ -model = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Category\Collection::class) - ->disableOriginalConstructor() - ->setMethodsExcept(['joinUrlRewrite', 'setStoreId', 'getStoreId']) - ->getMock(); - } - - public function testStoreIdUsedByUrlRewrite() - { - $cond = '{{table}}.is_autogenerated = 1 AND {{table}}.store_id = 100 AND {{table}}.entity_type = \'category\''; - $this->model->expects($this->once()) - ->method('joinTable') - ->with( - $this->anything(), - $this->anything(), - $this->anything(), - $this->equalTo($cond), - $this->anything() - ); - $this->model->setStoreId(100); - $this->model->joinUrlRewrite(); - } -} From f4fc5b9899879e42beb14d891fa79626ced110bd Mon Sep 17 00:00:00 2001 From: "mastuhin.olexnadr" Date: Fri, 6 Apr 2018 12:07:22 +0300 Subject: [PATCH 039/236] ENGCOM-1103: [Forwardport] Category\Collection::joinUrlRewrite should use the store set on the collection #14381 --- .../Model/Category/ProductIndexerTest.php | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Category/ProductIndexerTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Category/ProductIndexerTest.php index c877ae1f388f8..5a7c70ebe8064 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/Category/ProductIndexerTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Category/ProductIndexerTest.php @@ -208,18 +208,28 @@ public function testCategoryCreate() } /** + * Finds 4 categories + * * @return Category[] */ private function getCategories() { - /** @var Category $category */ - $category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Category::class + $collectionFactory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory::class ); - $result = $category->getCollection()->addAttributeToSelect('name')->getItems(); - $result = array_slice($result, 2); + /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $collection */ + $collection = $collectionFactory->create(); + + $collection + ->addAttributeToSelect('name') + ->addAttributeToFilter('name', ['in' => [ + 'Category 1', + 'Category 2', + 'Category 3', + 'Category 4', + ]]); - return array_slice($result, 0, 4); + return array_values($collection->getItems()); } } From 475ae2315638ec11e3a2d6754f080ce0ce3148b5 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Mon, 9 Apr 2018 15:30:35 -0500 Subject: [PATCH 040/236] MAGETWO-90241: Image Uploader maxFileSize configuration can exceed PHP's upload_max_filesize Use the minimum of the two configurations if both are present --- .../Ui/Component/Form/Element/DataType/Media/Image.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php index 130d5037e77f7..cab96d796c63e 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php @@ -64,7 +64,10 @@ public function getComponentName() public function prepare() { // dynamically set max file size based on php ini config if not present in XML - $maxFileSize = $this->getConfiguration()['maxFileSize'] ?? $this->fileSize->getMaxFileSize(); + $maxFileSize = min(array_filter([ + $this->getConfiguration()['maxFileSize'] ?? null, + $this->fileSize->getMaxFileSize() + ])); $data = array_replace_recursive( $this->getData(), From b9732d7d1055c03fb353254b32a30b154234be67 Mon Sep 17 00:00:00 2001 From: Eric COURTIAL Date: Mon, 9 Apr 2018 19:46:39 -0400 Subject: [PATCH 041/236] Added possibility to select PHP input file mode --- lib/internal/Magento/Framework/File/Csv.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/internal/Magento/Framework/File/Csv.php b/lib/internal/Magento/Framework/File/Csv.php index 3d177f89e2968..67177445a670d 100644 --- a/lib/internal/Magento/Framework/File/Csv.php +++ b/lib/internal/Magento/Framework/File/Csv.php @@ -126,13 +126,17 @@ public function getDataPairs($file, $keyIndex = 0, $valueIndex = 1) /** * Saving data row array into file * - * @param string $file - * @param array $data - * @return $this + * @param string $file + * @param array $data + * @param string $mode + * + * @return $this + * + * @throws \Magento\Framework\Exception\FileSystemException */ - public function saveData($file, $data) + public function saveData($file, $data, $mode = 'w') { - $fh = fopen($file, 'w'); + $fh = fopen($file, $mode); foreach ($data as $dataRow) { $this->file->filePutCsv($fh, $dataRow, $this->_delimiter, $this->_enclosure); } From c2641b576e01a1bcd3618cc222ae823cecd6a54e Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Wed, 11 Apr 2018 17:00:53 +0300 Subject: [PATCH 042/236] MAGETWO-90055: When querying for products filtered by category_id, response has all_children = null for categories --- app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php index 52ecdccecdc3f..6908646624e6a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php @@ -112,6 +112,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $categories[$item->getId()] = $this->customAttributesFlattener ->flaternize($categories[$item->getId()]); $categories[$item->getId()]['product_count'] = $item->getProductCount(); + $categories[$item->getId()]['all_children'] = $item->getAllChildren(); } } From 68e087843fbb17a2c3de1ed4ecd2c380feab8640 Mon Sep 17 00:00:00 2001 From: Eric COURTIAL Date: Wed, 11 Apr 2018 19:11:17 -0400 Subject: [PATCH 043/236] Added a new method to replace the saveData one --- lib/internal/Magento/Framework/File/Csv.php | 34 +++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/internal/Magento/Framework/File/Csv.php b/lib/internal/Magento/Framework/File/Csv.php index 67177445a670d..10ee9b193d543 100644 --- a/lib/internal/Magento/Framework/File/Csv.php +++ b/lib/internal/Magento/Framework/File/Csv.php @@ -126,21 +126,43 @@ public function getDataPairs($file, $keyIndex = 0, $valueIndex = 1) /** * Saving data row array into file * - * @param string $file - * @param array $data + * @param string $file + * @param array $data + * @return $this + * @throws \Magento\Framework\Exception\FileSystemException + * @deprecated + * @see appendData + */ + public function saveData($file, $data) + { + $fh = fopen($file, 'w'); + foreach ($data as $dataRow) { + $this->file->filePutCsv($fh, $dataRow, $this->_delimiter, $this->_enclosure); + } + fclose($fh); + return $this; + } + + /** + * Replace the saveData method by + * allowing to select the input mode + * + * @param $file + * @param $data * @param string $mode * * @return $this * * @throws \Magento\Framework\Exception\FileSystemException */ - public function saveData($file, $data, $mode = 'w') + public function appendData($file, $data, $mode = 'w') { - $fh = fopen($file, $mode); + $fileHandler = fopen($file, $mode); foreach ($data as $dataRow) { - $this->file->filePutCsv($fh, $dataRow, $this->_delimiter, $this->_enclosure); + $this->file->filePutCsv($fileHandler, $dataRow, $this->_delimiter, $this->_enclosure); } - fclose($fh); + fclose($fileHandler); + return $this; } } From feab5c0508e3fb01c2be316164e7da4bdd2687ee Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Fri, 13 Apr 2018 16:16:30 +0300 Subject: [PATCH 044/236] MAGETWO-90055: Removing all_children field from categories --- app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php | 1 - .../Model/Resolver/Products/DataProvider/CategoryTree.php | 1 - .../testsuite/Magento/GraphQl/Catalog/CategoryTest.php | 1 - 3 files changed, 3 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php index 6908646624e6a..52ecdccecdc3f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php @@ -112,7 +112,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $categories[$item->getId()] = $this->customAttributesFlattener ->flaternize($categories[$item->getId()]); $categories[$item->getId()]['product_count'] = $item->getProductCount(); - $categories[$item->getId()]['all_children'] = $item->getAllChildren(); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 6d3bb35d654e2..1318c8b9a0640 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -133,7 +133,6 @@ private function hydrateCategory(CategoryInterface $category) : array $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); $categoryData['id'] = $category->getId(); $categoryData['product_count'] = $category->getProductCount(); - $categoryData['all_children'] = $category->getAllChildren(); $categoryData['children'] = []; $categoryData['available_sort_by'] = $category->getAvailableSortBy(); return $this->customAttributesFlattener->flaternize($categoryData); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index e193b556fa942..98fdd2d421934 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -38,7 +38,6 @@ public function testCategoriesTree() level is_active description - all_children path path_in_store product_count From eddf777809dbbd6d11cf0c363429f07429a8b034 Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Mon, 16 Apr 2018 16:47:21 +0300 Subject: [PATCH 045/236] MAGETWO-89924: Category query returns more sub categories (and categories outside of the requested category_id) than it should + remove nested children --- .../Model/Resolver/Products/DataProvider/CategoryTree.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 1318c8b9a0640..8b1f8aeb86d49 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -94,6 +94,7 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $depth = $this->calculateDepth($categoryQuery); $level = $this->getLevelByRootCategoryId($rootCategoryId); //Search for desired part of category tree + $collection->addPathFilter(sprintf('.*/%s/[/0-9]*$', $rootCategoryId)); $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); From 23264f586c4eabb3abe6faa061b39c4a1e63afb5 Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Wed, 18 Apr 2018 16:56:36 -0500 Subject: [PATCH 046/236] MAGETWO-90055: Removing all_children field from categories - refactor category query - remove multiple fields - fixing tests --- .../Model/Config/CategoryAttributeReader.php | 10 ++++++++- .../Model/Resolver/CategoryTree.php | 14 +++++++------ .../CatalogGraphQl/etc/schema.graphqls | 20 ++++-------------- .../Magento/GraphQl/Catalog/CategoryTest.php | 21 +++++++------------ .../GraphQl/Catalog/ProductSearchTest.php | 12 ++++++----- .../GraphQl/Catalog/ProductViewTest.php | 5 ----- 6 files changed, 36 insertions(+), 46 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php index 0b1e76313b46d..0ca72d9ff9519 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/CategoryAttributeReader.php @@ -30,7 +30,15 @@ class CategoryAttributeReader implements ReaderInterface 'is_active', 'children', 'level', - 'default_sort_by' + 'default_sort_by', + 'all_children', + 'page_layout', + 'custom_design', + 'custom_design_from', + 'custom_design_to', + 'custom_layout_update', + 'custom_use_parent_settings', + 'custom_apply_to_products', ]; /** diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index c78237b7bcd45..f631e5ff61d2e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -55,11 +55,11 @@ public function __construct( */ private function assertFiltersAreValidAndGetCategoryRootIds(array $args) : int { - if (!isset($args['filter']['root_category_id'])) { - throw new GraphQlInputException(__('"root_category_id" filter should be specified')); + if (!isset($args['id'])) { + throw new GraphQlInputException(__('"id for category should be specified')); } - return (int) $args['filter']['root_category_id']; + return (int) $args['id']; } /** @@ -74,9 +74,11 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryId = $this->assertFiltersAreValidAndGetCategoryRootIds($args); $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); - return [ - 'category_tree' => reset($categoriesTree) - ]; + if (!empty($categoriesTree)) { + return current($categoriesTree); + } else { + return null; + } }); } } diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index f223b1c9eec07..ca1ff78654319 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -10,9 +10,9 @@ type Query { sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): Products @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes") - categories ( - filter: CategoryFilterInput @doc(description: "Filter for categories") - ): Categories + category ( + id: Int @doc(description: "Id of the category") + ): CategoryTree @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") } @@ -263,9 +263,6 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") new_to_date: String @doc(description: "The end date for new product listings") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") tier_price: Float @doc(description: "The price when tier pricing is in effect and the items purchased threshold has been reached") - custom_layout_update: String @doc(description: "XML code that is applied as a layout update to the product page") - custom_layout: String @doc(description: "The name of a custom layout") - category_ids: [Int] @doc(description: "An array of category IDs the product belongs to") options_container: String @doc(description: "If the product has multiple options, determines where they appear on the product page") image_label: String @doc(description: "The label assigned to a product image") small_image_label: String @doc(description: "The label assigned to a product's small image") @@ -300,11 +297,7 @@ type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the max_characters: Int @doc(description: "The maximum number of characters that can be entered for this customizable option") } -type Categories @doc(description: "Categories aggregates the category tree of a product") { - category_tree: CategoryTree @doc(description: "Tree of categories") -} - -type CategoryTree implements CategoryInterface @doc(description: "Category Tree") { +type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation") { children: [CategoryTree] @doc(description: "Child categories tree") @resolve(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree") } @@ -376,7 +369,6 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model path_in_store: String @doc(description: "Category path in store") url_key: String @doc(description: "The url key assigned to the category") url_path: String @doc(description: "The url path assigned to the category") - is_active: Boolean @doc(description: "Indicates whether the category is enabled") position: Int @doc(description: "The position of the category relative to other categories at the same level in tree") level: Int @doc(description: "Indicates the depth of the category within the tree") created_at: String @doc(description: "Timestamp indicating when the category was created") @@ -412,10 +404,6 @@ type Products @doc(description: "The Products object is the top-level object ret filters: [LayerFilter] @doc(description: "Layered navigation filters array") } -input CategoryFilterInput @doc(description: "Identifies which category attributes to search for and return.") { - root_category_id: Int @doc(description: "Id of the root category in the category tree to use as the starting point of your search.") -} - input ProductFilterInput @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.") sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 98fdd2d421934..eb138d738ea10 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -32,11 +32,9 @@ public function testCategoriesTree() $rootCategoryId = 2; $query = <<getData('categories/category_tree/children/7/children/1/description') + $responseDataObject->getData('category/children/7/children/1/description') ); self::assertEquals( 'default-category', - $responseDataObject->getData('categories/category_tree/url_key') + $responseDataObject->getData('category/url_key') ); self::assertEquals( [], - $responseDataObject->getData('categories/category_tree/children/0/available_sort_by') + $responseDataObject->getData('category/children/0/available_sort_by') ); self::assertEquals( 'name', - $responseDataObject->getData('categories/category_tree/children/0/default_sort_by') + $responseDataObject->getData('category/children/0/default_sort_by') ); self::assertCount( 8, - $responseDataObject->getData('categories/category_tree/children') + $responseDataObject->getData('category/children') ); self::assertCount( 2, - $responseDataObject->getData('categories/category_tree/children/7/children') + $responseDataObject->getData('category/children/7/children') ); self::assertEquals( 5, - $responseDataObject->getData('categories/category_tree/children/7/children/1/children/0/id') + $responseDataObject->getData('category/children/7/children/1/children/0/id') ); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index b6866c1374069..19bd2f22bb396 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -616,7 +616,9 @@ public function testFilteringForProductInMultipleCategories() sku name attribute_set_id - category_ids + categories { + id + } } } } @@ -630,11 +632,11 @@ public function testFilteringForProductInMultipleCategories() $product = $productRepository->get('simple333'); $categoryIds = $product->getCategoryIds(); foreach ($categoryIds as $index => $value) { - $categoryIds[$index] = (int)$value; + $categoryIds[$index] = [ 'id' => (int)$value]; } - $this->assertNotEmpty($response['products']['items'][0]['category_ids'], "Category_ids must not be empty"); - $this->assertNotNull($response['products']['items'][0]['category_ids'], "categoy_ids must not be null"); - $this->assertEquals($categoryIds, $response['products']['items'][0]['category_ids']); + $this->assertNotEmpty($response['products']['items'][0]['categories'], "Category_ids must not be empty"); + $this->assertNotNull($response['products']['items'][0]['categories'], "categoy_ids must not be null"); + $this->assertEquals($categoryIds, $response['products']['items'][0]['categories']); /** @var MetadataPool $metaData */ $metaData = ObjectManager::getInstance()->get(MetadataPool::class); $linkField = $metaData->getMetadata(ProductInterface::class)->getLinkField(); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 34c5402a3e8d0..9a4101e5602af 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -44,8 +44,6 @@ public function testQueryAllFieldsSimpleProduct() attribute_set_id country_of_manufacture created_at - custom_layout - custom_layout_update description gift_message_available id @@ -292,15 +290,12 @@ public function testQueryMediaGalleryEntryFieldsSimpleProduct() { items{ attribute_set_id - category_ids categories { id } country_of_manufacture created_at - custom_layout - custom_layout_update description gift_message_available id From 44b703910a6fc55b6a377bff1631c11127fb309d Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Thu, 19 Apr 2018 20:00:53 +0300 Subject: [PATCH 047/236] MAGETWO-89924: Category query returns more sub categories (and categories outside of the requested category_id) than it should + remove nested children --- .../Magento/GraphQl/Catalog/CategoryTest.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 98fdd2d421934..5e16f2c33725b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -22,6 +22,109 @@ protected function setUp() $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories_no_products.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param int $categoryId + * @param array $expectedResponse + * + * @dataProvider categorySubtreeTestDataProvider + */ + public function testCategoriesSubtree($categoryId, $expectedResponse) + { + $query = <<objectManager->create( + \Magento\Integration\Api\CustomerTokenServiceInterface::class + ); + $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals($expectedResponse, $response); + } + + public function categorySubtreeTestDataProvider() + { + return [ + [ + 'category_id' => 3, + 'expected_subtree' => [ + 'categories' => [ + 'category_tree' => [ + 'id' => 3, + 'level' => 2, + 'name' => 'Category 1', + 'path' => '1/2/3', + 'product_count' => 0, + 'children' => [ + [ + 'id' => 4, + 'name' => 'Category 1.1', + 'level' => 3, + 'is_active' => true, + 'path' => '1/2/3/4', + 'children' => [ + [ + 'id' => 5, + 'name' => 'Category 1.1.1', + 'level' => 4, + 'description' => NULL, + 'path' => '1/2/3/4/5', + ], + ], + ], + ], + ], + ], + ] + ], + [ + 'category_id' => 6, + 'expected_subtree' => [ + 'categories' => [ + 'category_tree' => [ + 'id' => 6, + 'level' => 2, + 'name' => 'Category 2', + 'path' => '1/2/6', + 'product_count' => 0, + 'children' => [] + ], + ], + ] + ] + ]; + } + /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Catalog/_files/categories.php From fd6aa643d41a8b9eae1e24ba5aea87bb449e3ff4 Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 04:46:16 +0200 Subject: [PATCH 048/236] Enable WebAPI interface to handle parameters through constructor DTO setter methods are no more required, parameters can be handled through constructor for immutable state approach A setter fallback is supported --- .../Webapi/ServiceInputProcessor.php | 45 ++++++++++++++++++- .../SimpleConstructor.php | 43 ++++++++++++++++++ .../ServiceInputProcessor/TestService.php | 23 +++++++--- .../Test/Unit/ServiceInputProcessorTest.php | 21 ++++++++- 4 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index f1df2b0ee53f0..0c32396897c12 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -65,6 +65,11 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $serviceTypeToEntityTypeMap; + /** + * @var \Magento\Framework\ObjectManager\ConfigInterface + */ + private $config; + /** * Initialize dependencies. * @@ -73,6 +78,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param AttributeValueFactory $attributeValueFactory * @param CustomAttributeTypeLocatorInterface $customAttributeTypeLocator * @param MethodsMap $methodsMap + * @param \Magento\Framework\ObjectManager\ConfigInterface $config * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap */ public function __construct( @@ -81,6 +87,7 @@ public function __construct( AttributeValueFactory $attributeValueFactory, CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, + \Magento\Framework\ObjectManager\ConfigInterface $config, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null ) { $this->typeProcessor = $typeProcessor; @@ -90,6 +97,7 @@ public function __construct( $this->methodsMap = $methodsMap; $this->serviceTypeToEntityTypeMap = $serviceTypeToEntityTypeMap ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); + $this->config = $config; } /** @@ -153,6 +161,33 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray return $inputData; } + /** + * @param string $className + * @param array $data + * @return array + * @throws \ReflectionException + */ + private function getConstructorData(string $className, array $data): array + { + $preferenceClass = $this->config->getPreference($className); + $class = new ClassReflection($preferenceClass ?: $className); + + $constructor = $class->getConstructor(); + if ($constructor === null) { + return []; + } + + $res = []; + $parameters = $constructor->getParameters(); + foreach ($parameters as $parameter) { + if (isset($data[$parameter->getName()])) { + $res[$parameter->getName()] = $data[$parameter->getName()]; + } + } + + return $res; + } + /** * Creates a new instance of the given class and populates it with the array of data. The data can * be in different forms depending on the adapter being used, REST vs. SOAP. For REST, the data is @@ -173,9 +208,17 @@ protected function _createFromArray($className, $data) if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { $className = substr($className, 0, -strlen('Interface')); } - $object = $this->objectManager->create($className); + // Primary method: assign to constructor parameters + $constructorArgs = $this->getConstructorData($className, $data); + $object = $this->objectManager->create($className, $constructorArgs); + + // Secondary method: fallback to setter methods foreach ($data as $propertyName => $value) { + if (isset($constructorArgs[$propertyName])) { + continue; + } + // Converts snake_case to uppercase CamelCase to help form getter/setter method names // This use case is for REST only. SOAP request data is already camel cased $camelCaseProperty = SimpleDataObjectConverter::snakeCaseToUpperCamelCase($propertyName); diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php new file mode 100644 index 0000000000000..a62fb70c50116 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php @@ -0,0 +1,43 @@ +entityId = $entityId; + $this->name = $name; + } + + /** + * @return int|null + */ + public function getEntityId() + { + return $this->entityId; + } + + /** + * @return string|null + */ + public function getName() + { + return $this->name; + } +} diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php index a5f91c101aa56..34515e8460b57 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/TestService.php @@ -5,11 +5,6 @@ */ namespace Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested; -use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray; - class TestService { const DEFAULT_VALUE = 42; @@ -25,6 +20,15 @@ public function simple($entityId, $name) return [$entityId, $name]; } + /** + * @param \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor $simpleConstructor + * @return \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor + */ + public function simpleConstructor(SimpleConstructor $simpleConstructor) + { + return $simpleConstructor; + } + /** * @param int $entityId * @return string[] @@ -34,6 +38,15 @@ public function simpleDefaultValue($entityId = self::DEFAULT_VALUE) return [$entityId]; } + /** + * @param int $entityId + * @return string[] + */ + public function constructorArguments($entityId = self::DEFAULT_VALUE) + { + return [$entityId]; + } + /** * @param \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested $nested * @return \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php index a4c3f00e374a8..3393b325a8f16 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; +use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleConstructor; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\WebapiBuilderFactory; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray; use Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray; @@ -59,8 +60,8 @@ protected function setUp() $this->objectManagerMock->expects($this->any()) ->method('create') ->willReturnCallback( - function ($className) use ($objectManager) { - return $objectManager->getObject($className); + function ($className, $arguments = []) use ($objectManager) { + return $objectManager->getObject($className, $arguments); } ); @@ -202,6 +203,22 @@ public function testNestedDataProperties() $this->assertEquals('Test', $details->getName()); } + public function testSimpleConstructorProperties() + { + $data = ['simpleConstructor' => ['entityId' => 15, 'name' => 'Test']]; + $result = $this->serviceInputProcessor->process( + \Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService::class, + 'simpleConstructor', + $data + ); + $this->assertNotNull($result); + $arg = $result[0]; + + $this->assertTrue($arg instanceof SimpleConstructor); + $this->assertEquals(15, $arg->getEntityId()); + $this->assertEquals('Test', $arg->getName()); + } + public function testSimpleArrayProperties() { $data = ['ids' => [1, 2, 3, 4]]; From 04a53a8ac6b051172659c0b493eba6f508b860af Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 12:49:46 -0700 Subject: [PATCH 049/236] FIX for static tests failure _createFromArray will not be fixed due to its eventual deprecation, since a new immutable approach is provided --- .../Magento/Framework/Webapi/ServiceInputProcessor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 0c32396897c12..f75044ec88c71 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -5,6 +5,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Webapi; use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; @@ -197,6 +199,7 @@ private function getConstructorData(string $className, array $data): array * @param array $data * @return object the newly created and populated object * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _createFromArray($className, $data) { From 68e5dcd8c3bf9ad3b897d264579434de62940b62 Mon Sep 17 00:00:00 2001 From: Riccardo Tempesta Date: Sun, 22 Apr 2018 13:13:05 -0700 Subject: [PATCH 050/236] FIX static tests, missing declare(strict_types=1) --- .../Test/Unit/ServiceInputProcessor/SimpleConstructor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php index a62fb70c50116..f457a96e22a37 100644 --- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php +++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessor/SimpleConstructor.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor; class SimpleConstructor From 2280f3c03fec286bc8c8d169a00cb1b8fa4cda09 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Tue, 24 Apr 2018 11:04:57 +0300 Subject: [PATCH 051/236] MAGETWO-90308: Wrong page cached for logged in user --- app/etc/di.xml | 5 +++ .../Magento/Framework/App/Response/Http.php | 19 +++++++++-- .../App/Test/Unit/Response/HttpTest.php | 32 +++++++++++++++++-- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/etc/di.xml b/app/etc/di.xml index 986652bc6bafe..6ee87383a873d 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1660,4 +1660,9 @@ + + + Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT + + diff --git a/lib/internal/Magento/Framework/App/Response/Http.php b/lib/internal/Magento/Framework/App/Response/Http.php index 5bda327ad6f51..62ff94e7043f5 100644 --- a/lib/internal/Magento/Framework/App/Response/Http.php +++ b/lib/internal/Magento/Framework/App/Response/Http.php @@ -9,10 +9,12 @@ use Magento\Framework\App\Http\Context; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Stdlib\Cookie\CookieMetadata; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Session\Config\ConfigInterface; class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response { @@ -50,25 +52,33 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response */ protected $dateTime; + /** + * @var \Magento\Framework\Session\Config\ConfigInterface + */ + private $sessionConfig; + /** * @param HttpRequest $request * @param CookieManagerInterface $cookieManager * @param CookieMetadataFactory $cookieMetadataFactory * @param Context $context * @param DateTime $dateTime + * @param ConfigInterface|null $sessionConfig */ public function __construct( HttpRequest $request, CookieManagerInterface $cookieManager, CookieMetadataFactory $cookieMetadataFactory, Context $context, - DateTime $dateTime + DateTime $dateTime, + ConfigInterface $sessionConfig = null ) { $this->request = $request; $this->cookieManager = $cookieManager; $this->cookieMetadataFactory = $cookieMetadataFactory; $this->context = $context; $this->dateTime = $dateTime; + $this->sessionConfig = $sessionConfig ?: ObjectManager::getInstance()->get(ConfigInterface::class); } /** @@ -91,8 +101,11 @@ public function sendVary() { $varyString = $this->context->getVaryString(); if ($varyString) { - $sensitiveCookieMetadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata()->setPath('/'); - $this->cookieManager->setSensitiveCookie(self::COOKIE_VARY_STRING, $varyString, $sensitiveCookieMetadata); + $cookieLifeTime = $this->sessionConfig->getCookieLifetime(); + $sensitiveCookMetadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata( + [CookieMetadata::KEY_DURATION => $cookieLifeTime] + )->setPath('/'); + $this->cookieManager->setSensitiveCookie(self::COOKIE_VARY_STRING, $varyString, $sensitiveCookMetadata); } elseif ($this->request->get(self::COOKIE_VARY_STRING)) { $cookieMetadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata()->setPath('/'); $this->cookieManager->deleteCookie(self::COOKIE_VARY_STRING, $cookieMetadata); diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php index 144fd5fbab4b8..efb35b7321c3b 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Response/HttpTest.php @@ -8,6 +8,8 @@ use Magento\Framework\App\Response\Http; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Session\Config\ConfigInterface; +use Magento\Framework\Stdlib\Cookie\CookieMetadata; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -49,6 +51,19 @@ class HttpTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * @var ConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionConfigMock; + + /** + * @var int + */ + private $cookieLifeTime = 3600; + + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -68,6 +83,10 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->sessionConfigMock = $this->getMockBuilder(ConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->model = $this->objectManager->getObject( \Magento\Framework\App\Response\Http::class, [ @@ -75,13 +94,17 @@ protected function setUp() 'cookieManager' => $this->cookieManagerMock, 'cookieMetadataFactory' => $this->cookieMetadataFactoryMock, 'context' => $this->contextMock, - 'dateTime' => $this->dateTimeMock + 'dateTime' => $this->dateTimeMock, + 'sessionConfig' => $this->sessionConfigMock, ] ); $this->model->headersSentThrowsException = false; $this->model->setHeader('Name', 'Value'); } + /** + * @inheritdoc + */ protected function tearDown() { unset($this->model); @@ -108,9 +131,14 @@ public function testSendVary() ->method('getVaryString') ->will($this->returnValue($expectedCookieValue)); + $this->sessionConfigMock->expects($this->once()) + ->method('getCookieLifetime') + ->willReturn($this->cookieLifeTime); + $this->cookieMetadataFactoryMock->expects($this->once()) ->method('createSensitiveCookieMetadata') - ->will($this->returnValue($sensitiveCookieMetadataMock)); + ->with([CookieMetadata::KEY_DURATION => $this->cookieLifeTime]) + ->willReturn($sensitiveCookieMetadataMock); $this->cookieManagerMock->expects($this->once()) ->method('setSensitiveCookie') From fdf2462c9307e0d4fa6679671445ae1e704b3fe9 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Tue, 24 Apr 2018 16:26:47 +0300 Subject: [PATCH 052/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- .../UrlRewrite/Model/Storage/DbStorage.php | 64 +++++++++-- .../Test/Unit/Model/Storage/DbStorageTest.php | 103 ++++-------------- 2 files changed, 79 insertions(+), 88 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index f418ffe4a1ad9..dc5faf3cebee8 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -7,6 +7,7 @@ use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; @@ -141,17 +142,61 @@ protected function doFindOneByData(array $data) } /** - * {@inheritdoc} + * Delete old URLs from DB. + * + * @param UrlRewrite[] $urls + * @return void */ - protected function doReplace(array $urls) + private function deleteOldUrls(array $urls): void { - foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) { - $urlData[UrlRewrite::ENTITY_TYPE] = $type; - // prevent query locking in a case when nothing to delete - if ($this->connection->fetchRow($this->prepareSelect($urlData))) { - $this->deleteByData($urlData); - } + $oldUrlsSelect = $this->connection->select(); + $oldUrlsSelect->from( + $this->resource->getTableName(self::TABLE_NAME) + ); + /** @var UrlRewrite $url */ + foreach ($urls as $url) { + $oldUrlsSelect->orWhere( + $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_TYPE + ) . ' = ?', + $url->getEntityType() + ); + $oldUrlsSelect->where( + $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_ID + ) . ' = ?', + $url->getEntityId() + ); + $oldUrlsSelect->where( + $this->connection->quoteIdentifier( + UrlRewrite::STORE_ID + ) . ' = ?', + $url->getStoreId() + ); + } + + // prevent query locking in a case when nothing to delete + $checkOldUrlsSelect = clone $oldUrlsSelect; + $checkOldUrlsSelect->reset(Select::COLUMNS); + $checkOldUrlsSelect->columns('count(*)'); + $hasOldUrls = (bool)$this->connection->fetchOne($checkOldUrlsSelect); + + if ($hasOldUrls) { + $this->connection->query( + $oldUrlsSelect->deleteFromSelect( + $this->resource->getTableName(self::TABLE_NAME) + ) + ); } + } + + /** + * @inheritDoc + */ + protected function doReplace(array $urls) + { + $this->deleteOldUrls($urls); + $data = []; foreach ($urls as $url) { $data[] = $url->toArray(); @@ -165,7 +210,7 @@ protected function doReplace(array $urls) $urlFound = $this->doFindOneByData( [ UrlRewriteData::REQUEST_PATH => $url->getRequestPath(), - UrlRewriteData::STORE_ID => $url->getStoreId() + UrlRewriteData::STORE_ID => $url->getStoreId(), ] ); if (isset($urlFound[UrlRewriteData::URL_REWRITE_ID])) { @@ -217,6 +262,7 @@ protected function insertMultiple($data) * * @param UrlRewrite[] $urls * @return array + * @deprecated Not used anymore. */ protected function createFilterDataBasedOnUrls($urls) { diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php index 41fd5b48ecca9..b96a8ef637404 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php @@ -6,6 +6,7 @@ namespace Magento\UrlRewrite\Test\Unit\Model\Storage; +use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\UrlRewrite\Model\Storage\DbStorage; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -49,10 +50,9 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->dataObjectHelper = $this->createMock(\Magento\Framework\Api\DataObjectHelper::class); $this->connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); - $this->select = $this->createPartialMock( - \Magento\Framework\DB\Select::class, - ['from', 'where', 'deleteFromSelect'] - ); + $this->select = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); $this->resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->resource->expects($this->any()) @@ -447,87 +447,32 @@ public function testReplace() // delete $urlFirst->expects($this->any()) - ->method('getByKey') - ->will($this->returnValueMap([ - [UrlRewrite::ENTITY_TYPE, 'product'], - [UrlRewrite::ENTITY_ID, 'entity_1'], - [UrlRewrite::STORE_ID, 'store_id_1'], - ])); - $urlFirst->expects($this->any())->method('getEntityType')->willReturn('product'); + ->method('getEntityType') + ->willReturn('product'); + $urlFirst->expects($this->any()) + ->method('getEntityId') + ->willReturn('entity_1'); + $urlFirst->expects($this->any()) + ->method('getStoreId') + ->willReturn('store_id_1'); + + $urlSecond->expects($this->any()) + ->method('getEntityType') + ->willReturn('category'); + $urlSecond->expects($this->any()) + ->method('getEntityId') + ->willReturn('entity_2'); $urlSecond->expects($this->any()) - ->method('getByKey') - ->will($this->returnValueMap([ - [UrlRewrite::ENTITY_TYPE, 'category'], - [UrlRewrite::ENTITY_ID, 'entity_2'], - [UrlRewrite::STORE_ID, 'store_id_2'], - ])); - $urlSecond->expects($this->any())->method('getEntityType')->willReturn('category'); + ->method('getStoreId') + ->willReturn('store_id_2'); $this->connectionMock->expects($this->any()) ->method('quoteIdentifier') ->will($this->returnArgument(0)); - $this->select->expects($this->at(1)) - ->method('where') - ->with('entity_id IN (?)', ['entity_1']); - - $this->select->expects($this->at(2)) - ->method('where') - ->with('store_id IN (?)', ['store_id_1']); - - $this->select->expects($this->at(3)) - ->method('where') - ->with('entity_type IN (?)', 'product'); - - $this->select->expects($this->at(5)) - ->method('where') - ->with('entity_id IN (?)', ['entity_1']); - - $this->select->expects($this->at(6)) - ->method('where') - ->with('store_id IN (?)', ['store_id_1']); - - $this->select->expects($this->at(7)) - ->method('where') - ->with('entity_type IN (?)', 'product'); - - $this->connectionMock->expects($this->any()) - ->method('fetchRow') - ->willReturn(['some-data']); - - $this->select->expects($this->at(8)) - ->method('deleteFromSelect') - ->with('table_name') - ->will($this->returnValue('sql delete query')); - - $this->select->expects($this->at(10)) - ->method('where') - ->with('entity_id IN (?)', ['entity_2']); - - $this->select->expects($this->at(11)) - ->method('where') - ->with('store_id IN (?)', ['store_id_2']); - - $this->select->expects($this->at(12)) - ->method('where') - ->with('entity_type IN (?)', 'category'); - - $this->select->expects($this->at(14)) - ->method('where') - ->with('entity_id IN (?)', ['entity_2']); - - $this->select->expects($this->at(15)) - ->method('where') - ->with('store_id IN (?)', ['store_id_2']); - - $this->select->expects($this->at(16)) - ->method('where') - ->with('entity_type IN (?)', 'category'); - - $this->select->expects($this->at(17)) - ->method('deleteFromSelect') - ->with('table_name') - ->will($this->returnValue('sql delete query')); + $this->select->expects($this->any()) + ->method($this->anything()) + ->willReturnSelf(); $this->resource->expects($this->any()) ->method('getTableName') From 18fd078863b5f92ba1be36abdf8c9f3823868da9 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Tue, 24 Apr 2018 17:56:11 +0300 Subject: [PATCH 053/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index dc5faf3cebee8..52b6e562a16ab 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -37,7 +37,7 @@ class DbStorage extends AbstractStorage protected $resource; /** - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ private $logger; @@ -45,7 +45,7 @@ class DbStorage extends AbstractStorage * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory * @param DataObjectHelper $dataObjectHelper * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Psr\Log\LoggerInterface|null $logger + * @param LoggerInterface|null $logger */ public function __construct( UrlRewriteFactory $urlRewriteFactory, @@ -56,7 +56,7 @@ public function __construct( $this->connection = $resource->getConnection(); $this->resource = $resource; $this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Psr\Log\LoggerInterface::class); + ->get(LoggerInterface::class); parent::__construct($urlRewriteFactory, $dataObjectHelper); } From 9b8af8797c622d72d3300f6471255e3916b09d0e Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Wed, 25 Apr 2018 08:55:49 +0300 Subject: [PATCH 054/236] MAGETWO-90149: Product Import does not allow store-specific Custom Option labels --- .../Model/Export/RowCustomizer.php | 143 ++++++++++++-- .../Model/Import/Product/Type/Bundle.php | 75 +++++-- .../Export/Product/RowCustomizerTest.php | 27 ++- .../Model/Import/Product/Type/BundleTest.php | 14 +- .../Magento/BundleImportExport/composer.json | 1 + .../Model/Export/Product.php | 64 +++--- .../Model/Import/Product/Option.php | 102 ++++++---- .../_files/product_with_multiple_options.php | 5 + .../Model/Import/Product/Type/BundleTest.php | 60 ++++++ .../import_bundle_multiple_store_views.csv | 5 + .../AbstractProductExportImportTestCase.php | 24 +-- .../Model/Export/ProductTest.php | 86 ++++++++ .../Model/Import/ProductTest.php | 185 +++++++++++++++--- .../_files/product_with_custom_options.csv | 2 +- ...ustom_options_and_multiple_store_views.csv | 4 + 15 files changed, 648 insertions(+), 149 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_multiple_store_views.csv create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv diff --git a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php index 4004a3e5f1933..b805855f363b2 100644 --- a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php +++ b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php @@ -9,9 +9,10 @@ use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface; use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel; use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection; -use Magento\ImportExport\Controller\Adminhtml\Import; use Magento\ImportExport\Model\Import as ImportModel; use \Magento\Catalog\Model\Product\Type\AbstractType; +use \Magento\Framework\App\ObjectManager; +use \Magento\Store\Model\StoreManagerInterface; /** * Class RowCustomizer @@ -105,6 +106,35 @@ class RowCustomizer implements RowCustomizerInterface AbstractType::SHIPMENT_SEPARATELY => 'separately', ]; + /** + * @var \Magento\Bundle\Model\ResourceModel\Option\Collection[] + */ + private $optionCollections = []; + + /** + * @var array + */ + private $storeIdToCode = []; + + /** + * @var string + */ + private $optionCollectionCacheKey = '_cache_instance_options_collection'; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + * @throws \RuntimeException + */ + public function __construct(StoreManagerInterface $storeManager) + { + $this->storeManager = $storeManager; + } + /** * Retrieve list of bundle specific columns * @return array @@ -207,15 +237,13 @@ protected function populateBundleData($collection) */ protected function getFormattedBundleOptionValues($product) { - /** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection */ - $optionsCollection = $product->getTypeInstance() - ->getOptionsCollection($product) - ->setOrder('position', Collection::SORT_ORDER_ASC); - + $optionCollections = $this->getProductOptionCollections($product); $bundleData = ''; - foreach ($optionsCollection as $option) { + $optionTitles = $this->getBundleOptionTitles($product); + foreach ($optionCollections->getItems() as $option) { + $optionValues = $this->getFormattedOptionValues($option, $optionTitles); $bundleData .= $this->getFormattedBundleSelections( - $this->getFormattedOptionValues($option), + $optionValues, $product->getTypeInstance() ->getSelectionsCollection([$option->getId()], $product) ->setOrder('position', Collection::SORT_ORDER_ASC) @@ -267,16 +295,23 @@ function ($value, $key) { * Retrieve option value of bundle product * * @param \Magento\Bundle\Model\Option $option + * @param string[] $optionTitles * @return string */ - protected function getFormattedOptionValues($option) + protected function getFormattedOptionValues($option, $optionTitles = []) { - return 'name' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getTitle() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR - . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR - . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR - . $option->getRequired(); + $names = implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_map( + function ($title, $storeName) { + return $storeName . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $title; + }, + $optionTitles[$option->getOptionId()], + array_keys($optionTitles[$option->getOptionId()]) + )); + return $names . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR + . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR + . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR + . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR + . $option->getRequired(); } /** @@ -381,4 +416,82 @@ private function parseAdditionalAttributes($additionalAttributes) } return $preparedAttributes; } + + /** + * Get product options titles. + * + * Values for all store views (default) should be specified with 'name' key. + * If user want to specify value or change existing for non default store views it should be specified with + * 'name_' prefix and needed store view suffix. + * + * For example: + * - 'name=All store views name' for all store views + * - 'name_specific_store=Specific store name' for store view with 'specific_store' store code + * + * @param \Magento\Catalog\Model\Product $product + * @return array + */ + private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): array + { + $optionCollections = $this->getProductOptionCollections($product); + $optionsTitles = []; + /** @var \Magento\Bundle\Model\Option $option */ + foreach ($optionCollections->getItems() as $option) { + $optionsTitles[$option->getId()]['name'] = $option->getTitle(); + } + $storeIds = $product->getStoreIds(); + if (count($storeIds) > 1) { + foreach ($storeIds as $storeId) { + $optionCollections = $this->getProductOptionCollections($product, $storeId); + /** @var \Magento\Bundle\Model\Option $option */ + foreach ($optionCollections->getItems() as $option) { + $optionTitle = $option->getTitle(); + if ($optionsTitles[$option->getId()]['name'] != $optionTitle) { + $optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById($storeId)] = $optionTitle; + } + } + } + } + return $optionsTitles; + } + + /** + * Get product options collection by provided product model. + * + * Set given store id to the product if it was defined (default store id will be set if was not). + * + * @param \Magento\Catalog\Model\Product $product $product + * @param int $storeId + * @return \Magento\Bundle\Model\ResourceModel\Option\Collection + */ + private function getProductOptionCollections( + \Magento\Catalog\Model\Product $product, + $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID + ): \Magento\Bundle\Model\ResourceModel\Option\Collection { + $productSku = $product->getSku(); + if (!isset($this->optionCollections[$productSku][$storeId])) { + $product->unsetData($this->optionCollectionCacheKey); + $product->setStoreId($storeId); + $this->optionCollections[$productSku][$storeId] = $product->getTypeInstance() + ->getOptionsCollection($product) + ->setOrder('position', Collection::SORT_ORDER_ASC); + } + return $this->optionCollections[$productSku][$storeId]; + } + + /** + * Retrieve store code by it's ID. + * + * Collect store id in $storeIdToCode[] private variable if it was not initialized earlier. + * + * @param int $storeId + * @return string + */ + private function getStoreCodeById($storeId): string + { + if (!isset($this->storeIdToCode[$storeId])) { + $this->storeIdToCode[$storeId] = $this->storeManager->getStore($storeId)->getCode(); + } + return $this->storeIdToCode[$storeId]; + } } diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 8e59c866e162a..6781eda632899 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -11,7 +11,8 @@ use \Magento\Framework\App\ObjectManager; use \Magento\Bundle\Model\Product\Price as BundlePrice; use \Magento\Catalog\Model\Product\Type\AbstractType; -use Magento\CatalogImportExport\Model\Import\Product; +use \Magento\CatalogImportExport\Model\Import\Product; +use \Magento\Store\Model\StoreManagerInterface; /** * Class Bundle @@ -136,6 +137,16 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst */ private $relationsDataSaver; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var array + */ + private $storeCodeToId = []; + /** * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac @@ -143,6 +154,7 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst * @param array $params * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool * @param Bundle\RelationsDataSaver|null $relationsDataSaver + * @param StoreManagerInterface $storeManager */ public function __construct( \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac, @@ -150,12 +162,15 @@ public function __construct( \Magento\Framework\App\ResourceConnection $resource, array $params, \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, - Bundle\RelationsDataSaver $relationsDataSaver = null + Bundle\RelationsDataSaver $relationsDataSaver = null, + StoreManagerInterface $storeManager = null ) { parent::__construct($attrSetColFac, $prodAttrColFac, $resource, $params, $metadataPool); $this->relationsDataSaver = $relationsDataSaver ?: ObjectManager::getInstance()->get(Bundle\RelationsDataSaver::class); + $this->storeManager = $storeManager + ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -262,20 +277,28 @@ protected function populateOptionTemplate($option, $entityId, $index = null) * @param array $option * @param int $optionId * @param int $storeId - * - * @return array|bool + * @return array */ protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) { - if (!isset($option['name']) || !isset($option['parent_id']) || !$optionId) { - return false; + $optionValues = []; + if (isset($option['name']) && isset($option['parent_id']) && $optionId) { + $pattern = '/^name[_]?(.*)/'; + $keys = array_keys($option); + $optionNames = preg_grep($pattern, $keys); + foreach ($optionNames as $optionName) { + preg_match($pattern, $optionName, $storeCodes); + $storeCode = array_pop($storeCodes); + $storeId = $storeCode ? $this->getStoreIdByCode($storeCode) : $storeId; + $optionValues[] = [ + 'option_id' => $optionId, + 'parent_product_id' => $option['parent_id'], + 'store_id' => $storeId, + 'title' => $option[$optionName], + ]; + } } - return [ - 'option_id' => $optionId, - 'parent_product_id' => $option['parent_id'], - 'store_id' => $storeId, - 'title' => $option['name'], - ]; + return $optionValues; } /** @@ -285,7 +308,7 @@ protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) * @param int $optionId * @param int $parentId * @param int $index - * @return array + * @return array|bool * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -576,21 +599,24 @@ protected function insertOptions() */ protected function populateInsertOptionValues($optionIds) { - $insertValues = []; + $optionValues = []; foreach ($this->_cachedOptions as $entityId => $options) { foreach ($options as $key => $option) { foreach ($optionIds as $optionId => $assoc) { if ($assoc['position'] == $this->_cachedOptions[$entityId][$key]['index'] && $assoc['parent_id'] == $entityId) { $option['parent_id'] = $entityId; - $insertValues[] = $this->populateOptionValueTemplate($option, $optionId); + $optionValues = array_merge( + $optionValues, + $this->populateOptionValueTemplate($option, $optionId) + ); $this->_cachedOptions[$entityId][$key]['option_id'] = $optionId; break; } } } } - return $insertValues; + return $optionValues; } /** @@ -711,4 +737,21 @@ protected function clear() $this->_cachedSkuToProducts = []; return $this; } + + /** + * Get store id by store code. + * + * @param string $storeCode + * @return int + */ + private function getStoreIdByCode(string $storeCode): int + { + if (!isset($this->storeIdToCode[$storeCode])) { + /** @var $store \Magento\Store\Model\Store */ + foreach ($this->storeManager->getStores() as $store) { + $this->storeCodeToId[$store->getCode()] = $store->getId(); + } + } + return $this->storeCodeToId[$storeCode]; + } } diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php index 849158122e8be..10e8a3443f5f9 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Export/Product/RowCustomizerTest.php @@ -52,14 +52,24 @@ class RowCustomizerTest extends \PHPUnit\Framework\TestCase */ protected $selection; + /** @var \Magento\Framework\App\ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeResolver; + /** * Set up */ protected function setUp() { $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->scopeResolver = $this->getMockBuilder(\Magento\Framework\App\ScopeResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getScope']) + ->getMockForAbstractClass(); $this->rowCustomizerMock = $this->objectManagerHelper->getObject( - \Magento\BundleImportExport\Model\Export\RowCustomizer::class + \Magento\BundleImportExport\Model\Export\RowCustomizer::class, + [ + 'scopeResolver' => $this->scopeResolver, + ] ); $this->productResourceCollection = $this->createPartialMock( \Magento\Catalog\Model\ResourceModel\Product\Collection::class, @@ -72,6 +82,8 @@ protected function setUp() 'getPriceType', 'getShipmentType', 'getSkuType', + 'getSku', + 'getStoreIds', 'getPriceView', 'getWeightType', 'getTypeInstance', @@ -79,6 +91,7 @@ protected function setUp() 'getSelectionsCollection' ] ); + $this->product->expects($this->any())->method('getStoreIds')->willReturn([1]); $this->product->expects($this->any())->method('getEntityId')->willReturn(1); $this->product->expects($this->any())->method('getPriceType')->willReturn(1); $this->product->expects($this->any())->method('getShipmentType')->willReturn(1); @@ -88,19 +101,20 @@ protected function setUp() $this->product->expects($this->any())->method('getTypeInstance')->willReturnSelf(); $this->optionsCollection = $this->createPartialMock( \Magento\Bundle\Model\ResourceModel\Option\Collection::class, - ['setOrder', 'getIterator'] + ['setOrder', 'getItems'] ); $this->product->expects($this->any())->method('getOptionsCollection')->willReturn($this->optionsCollection); $this->optionsCollection->expects($this->any())->method('setOrder')->willReturnSelf(); $this->option = $this->createPartialMock( \Magento\Bundle\Model\Option::class, - ['getId', 'getTitle', 'getType', 'getRequired'] + ['getId', 'getOptionId', 'getTitle', 'getType', 'getRequired'] ); $this->option->expects($this->any())->method('getId')->willReturn(1); + $this->option->expects($this->any())->method('getOptionId')->willReturn(1); $this->option->expects($this->any())->method('getTitle')->willReturn('title'); $this->option->expects($this->any())->method('getType')->willReturn(1); $this->option->expects($this->any())->method('getRequired')->willReturn(1); - $this->optionsCollection->expects($this->any())->method('getIterator')->will( + $this->optionsCollection->expects($this->any())->method('getItems')->will( $this->returnValue(new \ArrayIterator([$this->option])) ); $this->selection = $this->createPartialMock( @@ -130,6 +144,7 @@ protected function setUp() $this->product->expects($this->any())->method('getSelectionsCollection')->willReturn( $this->selectionsCollection ); + $this->product->expects($this->any())->method('getSku')->willReturn(1); $this->productResourceCollection->expects($this->any())->method('addAttributeToFilter')->willReturnSelf(); $this->productResourceCollection->expects($this->any())->method('getIterator')->will( $this->returnValue(new \ArrayIterator([$this->product])) @@ -141,6 +156,8 @@ protected function setUp() */ public function testPrepareData() { + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($scope); $result = $this->rowCustomizerMock->prepareData($this->productResourceCollection, [1]); $this->assertNotNull($result); } @@ -168,6 +185,8 @@ public function testAddHeaderColumns() */ public function testAddData() { + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($scope); $preparedData = $this->rowCustomizerMock->prepareData($this->productResourceCollection, [1]); $attributes = 'attribute=1,sku_type=1,attribute2="Text",price_type=1,price_view=1,weight_type=1,' . 'values=values,shipment_type=1,attribute3=One,Two,Three'; diff --git a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php index 8e1243b5eb3af..b0794f4564645 100644 --- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php +++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php @@ -58,6 +58,9 @@ class BundleTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm */ protected $setCollection; + /** @var \Magento\Framework\App\ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeResolver; + /** * * @return void @@ -170,14 +173,18 @@ protected function setUp() 0 => $this->entityModel, 1 => 'bundle' ]; - + $this->scopeResolver = $this->getMockBuilder(\Magento\Framework\App\ScopeResolverInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getScope']) + ->getMockForAbstractClass(); $this->bundle = $this->objectManagerHelper->getObject( \Magento\BundleImportExport\Model\Import\Product\Type\Bundle::class, [ 'attrSetColFac' => $this->attrSetColFac, 'prodAttrColFac' => $this->prodAttrColFac, 'resource' => $this->resource, - 'params' => $this->params + 'params' => $this->params, + 'scopeResolver' => $this->scopeResolver, ] ); @@ -214,7 +221,8 @@ public function testSaveData($skus, $bunch, $allowImport) $this->entityModel->expects($this->any())->method('isRowAllowedToImport')->will($this->returnValue( $allowImport )); - + $scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class)->getMockForAbstractClass(); + $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($scope); $this->connection->expects($this->any())->method('fetchAssoc')->with($this->select)->will($this->returnValue([ '1' => [ 'option_id' => '1', diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index 7b46703fc0ef5..42d9dc558d31e 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -8,6 +8,7 @@ "php": "~7.1.3||~7.2.0", "magento/framework": "*", "magento/module-bundle": "*", + "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index c4148cd90088a..23aa8d65ddb0d 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -5,6 +5,7 @@ */ namespace Magento\CatalogImportExport\Model\Export; +use Magento\Catalog\Model\ResourceModel\Product\Option\Collection; use Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor; use Magento\ImportExport\Model\Import; use \Magento\Store\Model\Store; @@ -203,7 +204,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity protected $_itemFactory; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Option\Collection + * @var Collection */ protected $_optionColFactory; @@ -1349,6 +1350,12 @@ protected function optionRowToCellString($option) } /** + * Collect custom options data for products that will be exported. + * + * Option name and type will be collected for all store views, all other data (which can't be changed on store view + * level will be collected for DEFAULT_STORE_ID only. + * Store view specified data will be saved to the additional store view row. + * * @param int[] $productIds * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -1359,49 +1366,48 @@ protected function getCustomOptionsData($productIds) $customOptionsData = []; foreach (array_keys($this->_storeIdToCode) as $storeId) { - if (Store::DEFAULT_STORE_ID != $storeId) { - continue; - } $options = $this->_optionColFactory->create(); - /* @var \Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options*/ - $options->reset(); - $options->addOrder('sort_order', 'ASC'); - $options->addTitleToResult($storeId); - $options->addPriceToResult($storeId); - $options->addProductToFilter($productIds); - $options->addValuesToResult($storeId); + /* @var Collection $options*/ + $options->reset() + ->addOrder('sort_order', Collection::SORT_ORDER_ASC) + ->addTitleToResult($storeId) + ->addPriceToResult($storeId) + ->addProductToFilter($productIds) + ->addValuesToResult($storeId); foreach ($options as $option) { $row = []; $productId = $option['product_id']; - $row['name'] = $option['title']; $row['type'] = $option['type']; - $row['required'] = $option['is_require']; - $row['price'] = $option['price']; - $row['price_type'] = ($option['price_type'] == 'percent') ? $option['price_type'] : 'fixed'; - $row['sku'] = $option['sku']; - if ($option['max_characters']) { - $row['max_characters'] = $option['max_characters']; - } - - foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { - if (!isset($option[$fileOptionKey])) { - continue; + if (Store::DEFAULT_STORE_ID === $storeId) { + $row['required'] = $option['is_require']; + $row['price'] = $option['price']; + $row['price_type'] = ($option['price_type'] === 'percent') ? 'percent' : 'fixed'; + $row['sku'] = $option['sku']; + if ($option['max_characters']) { + $row['max_characters'] = $option['max_characters']; } - $row[$fileOptionKey] = $option[$fileOptionKey]; - } + foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { + if (!isset($option[$fileOptionKey])) { + continue; + } + $row[$fileOptionKey] = $option[$fileOptionKey]; + } + } $values = $option->getValues(); if ($values) { foreach ($values as $value) { - $valuePriceType = ($value['price_type'] == 'percent') ? $value['price_type'] : 'fixed'; $row['option_title'] = $value['title']; - $row['price'] = $value['price']; - $row['price_type'] = $valuePriceType; - $row['sku'] = $value['sku']; + if (Store::DEFAULT_STORE_ID === $storeId) { + $row['option_title'] = $value['title']; + $row['price'] = $value['price']; + $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed'; + $row['sku'] = $value['sku']; + } $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row); } } else { diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 2a5b009733d36..38ffddd9924cf 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -12,6 +12,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection as ProductOptionValueCollection; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory as ProductOptionValueCollectionFactory; +use Magento\Store\Model\Store; /** * Entity class which provide possibility to import product custom options @@ -495,7 +496,7 @@ protected function _initStores(array $data) if (isset($data['stores'])) { $this->_storeCodeToId = $data['stores']; } else { - /** @var $store \Magento\Store\Model\Store */ + /** @var $store Store */ foreach ($this->_storeManager->getStores(true) as $store) { $this->_storeCodeToId[$store->getCode()] = $store->getId(); } @@ -759,7 +760,7 @@ protected function _findExistingOptionId(array $newOptionData, array $newOptionT ksort($newOptionTitles); $existingOptions = $this->_oldCustomOptions[$productId]; foreach ($existingOptions as $optionId => $optionData) { - if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'] == $newOptionTitles) { + if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'][0] == $newOptionTitles[0]) { return $optionId; } } @@ -854,7 +855,7 @@ protected function _saveNewOptionData(array $rowData, $rowNumber) $storeCode = $rowData[self::COLUMN_STORE]; $storeId = $this->_storeCodeToId[$storeCode]; } else { - $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $storeId = Store::DEFAULT_STORE_ID; } if (isset($this->_productsSkuToId[$this->_rowProductSku])) { // save in existing data array @@ -1121,17 +1122,44 @@ private function processOptionRow($name, $optionRow) { $result = [ self::COLUMN_TYPE => $name ? $optionRow['type'] : '', - self::COLUMN_IS_REQUIRED => $optionRow['required'], - self::COLUMN_ROW_SKU => $optionRow['sku'], - self::COLUMN_PREFIX . 'sku' => $optionRow['sku'], self::COLUMN_ROW_TITLE => '', self::COLUMN_ROW_PRICE => '' ]; + $result = $this->addPriceData($result, $optionRow); + + if (isset($optionRow['_custom_option_store'])) { + $result[self::COLUMN_STORE] = $optionRow['_custom_option_store']; + } + if (isset($optionRow['required'])) { + $result[self::COLUMN_IS_REQUIRED] = $optionRow['required']; + } + if (isset($optionRow['sku'])) { + $result[self::COLUMN_ROW_SKU] = $optionRow['sku']; + $result[self::COLUMN_PREFIX . 'sku'] = $optionRow['sku']; + } if (isset($optionRow['option_title'])) { $result[self::COLUMN_ROW_TITLE] = $optionRow['option_title']; } + if (isset($optionRow['max_characters'])) { + $result[$this->columnMaxCharacters] = $optionRow['max_characters']; + } + + $result = $this->addFileOptions($result, $optionRow); + + return $result; + } + + /** + * Adds price data. + * + * @param array $result + * @param array $optionRow + * @return array + */ + private function addPriceData($result, $optionRow): array + { if (isset($optionRow['price'])) { $percent_suffix = ''; if (isset($optionRow['price_type']) && $optionRow['price_type'] == 'percent') { @@ -1142,12 +1170,6 @@ private function processOptionRow($name, $optionRow) $result[self::COLUMN_PREFIX . 'price'] = $result[self::COLUMN_ROW_PRICE]; - if (isset($optionRow['max_characters'])) { - $result[$this->columnMaxCharacters] = $optionRow['max_characters']; - } - - $result = $this->addFileOptions($result, $optionRow); - return $result; } @@ -1172,7 +1194,8 @@ private function addFileOptions($result, $optionRow) } /** - * Import data rows + * Import data rows. + * Additional store view data (option titles) will be sought in store view specified import file rows * * @return boolean * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -1186,7 +1209,8 @@ protected function _importData() $this->_tables['catalog_product_option_type_value'] ); $prevOptionId = 0; - + $optionId = null; + $valueId = null; while ($bunch = $this->_dataSourceModel->getNextBunch()) { $products = []; $options = []; @@ -1199,6 +1223,12 @@ protected function _importData() $childCount = []; foreach ($bunch as $rowNumber => $rowData) { + if (isset($optionId, $valueId) && empty($rowData[PRODUCT::COL_STORE_VIEW_CODE])) { + $nextOptionId = $optionId; + $nextValueId = $valueId; + } + $optionId = $nextOptionId; + $valueId = $nextValueId; $multiRowData = $this->_getMultiRowFormat($rowData); foreach ($multiRowData as $optionData) { @@ -1213,7 +1243,7 @@ protected function _importData() $optionData = $this->_collectOptionMainData( $combinedData, $prevOptionId, - $nextOptionId, + $optionId, $products, $prices ); @@ -1223,7 +1253,7 @@ protected function _importData() $this->_collectOptionTypeData( $combinedData, $prevOptionId, - $nextValueId, + $valueId, $typeValues, $typePrices, $typeTitles, @@ -1306,7 +1336,9 @@ protected function _collectOptionMainData( $optionData = null; if ($this->_rowIsMain) { - $optionData = $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType); + $optionData = empty($rowData[Product::COL_STORE_VIEW_CODE]) + ? $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType) + : ''; if (!$this->_isRowHasSpecificType($this->_rowType) && ($priceData = $this->_getPriceData($rowData, $nextOptionId, $this->_rowType)) @@ -1337,6 +1369,7 @@ protected function _collectOptionMainData( * @param array &$childCount * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _collectOptionTypeData( array $rowData, @@ -1355,43 +1388,28 @@ protected function _collectOptionTypeData( $typeValues[$prevOptionId][] = $specificTypeData['value']; // ensure default title is set - if (!isset($typeTitles[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID])) { - $typeTitles[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = - $specificTypeData['title']; + if (!isset($typeTitles[$nextValueId][Store::DEFAULT_STORE_ID])) { + $typeTitles[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['title']; } if ($specificTypeData['price']) { if ($this->_isPriceGlobal) { - $typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = - $specificTypeData['price']; + $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; } else { // ensure default price is set - if (!isset($typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID])) { - $typePrices[$nextValueId][\Magento\Store\Model\Store::DEFAULT_STORE_ID] = - $specificTypeData['price']; + if (!isset($typePrices[$nextValueId][Store::DEFAULT_STORE_ID])) { + $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; } $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price']; } } $nextValueId++; - if (isset($parentCount[$prevOptionId])) { - $parentCount[$prevOptionId]++; - } else { - $parentCount[$prevOptionId] = 1; - } - } - - if (!isset($childCount[$this->_rowStoreId][$prevOptionId])) { - $childCount[$this->_rowStoreId][$prevOptionId] = 0; } - $parentValueId = $nextValueId - $parentCount[$prevOptionId] - + $childCount[$this->_rowStoreId][$prevOptionId]; - $specificTypeData = $this->_getSpecificTypeData($rowData, $parentValueId, false); + $specificTypeData = $this->_getSpecificTypeData($rowData, 0, false); //For others stores if ($specificTypeData) { - $typeTitles[$parentValueId][$this->_rowStoreId] = $specificTypeData['title']; - $childCount[$this->_rowStoreId][$prevOptionId]++; + $typeTitles[$nextValueId++][$this->_rowStoreId] = $specificTypeData['title']; } } } @@ -1406,7 +1424,7 @@ protected function _collectOptionTypeData( */ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$titles) { - $defaultStoreId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $defaultStoreId = Store::DEFAULT_STORE_ID; if (!empty($rowData[self::COLUMN_TITLE])) { if (!isset($titles[$prevOptionId][$defaultStoreId])) { // ensure default title is set @@ -1530,7 +1548,7 @@ protected function _parseRequiredData(array $rowData) } $this->_rowStoreId = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; } else { - $this->_rowStoreId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $this->_rowStoreId = Store::DEFAULT_STORE_ID; } // Init option type and set param which tell that row is main if (!empty($rowData[self::COLUMN_TYPE])) { @@ -1644,7 +1662,7 @@ protected function _getPriceData(array $rowData, $optionId, $type) ) { $priceData = [ 'option_id' => $optionId, - 'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, + 'store_id' => Store::DEFAULT_STORE_ID, 'price_type' => 'fixed', ]; diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php index efe15f5977a58..a8fdaa26380a2 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php @@ -47,6 +47,7 @@ 'default_title' => 'Option 1', 'type' => 'select', 'required' => 1, + 'position' => 1, 'delete' => '', ], // Required "Radio Buttons" option @@ -55,6 +56,7 @@ 'default_title' => 'Option 2', 'type' => 'radio', 'required' => 1, + 'position' => 2, 'delete' => '', ], // Required "Checkbox" option @@ -63,6 +65,7 @@ 'default_title' => 'Option 3', 'type' => 'checkbox', 'required' => 1, + 'position' => 3, 'delete' => '', ], // Required "Multiple Select" option @@ -71,6 +74,7 @@ 'default_title' => 'Option 4', 'type' => 'multi', 'required' => 1, + 'position' => 4, 'delete' => '', ], // Non-required "Multiple Select" option @@ -79,6 +83,7 @@ 'default_title' => 'Option 5', 'type' => 'multi', 'required' => 0, + 'position' => 5, 'delete' => '', ] ] diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php index 4a3d5d2e28afe..59ef01d7c581c 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php @@ -119,4 +119,64 @@ public function testBundleImport() } } } + + /** + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoAppArea adminhtml + * @return void + */ + public function testBundleImportWithMultipleStoreViews(): void + { + // import data from CSV file + $pathToFile = __DIR__ . '/../../_files/import_bundle_multiple_store_views.csv'; + $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => $pathToFile, + 'directory' => $directory, + ] + ); + $errors = $this->model->setSource($source) + ->setParameters( + [ + 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, + 'entity' => 'catalog_product', + ] + )->validateData(); + $this->assertTrue($errors->getErrorsCount() == 0); + $this->model->importData(); + $resource = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Product::class); + $productId = $resource->getIdBySku(self::TEST_PRODUCT_NAME); + $this->assertTrue(is_numeric($productId)); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $product->load($productId); + $this->assertFalse($product->isObjectNew()); + $this->assertEquals(self::TEST_PRODUCT_NAME, $product->getName()); + $this->assertEquals(self::TEST_PRODUCT_TYPE, $product->getTypeId()); + $this->assertEquals(1, $product->getShipmentType()); + $optionIdList = $resource->getProductsIdsBySkus($this->optionSkuList); + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $i = 0; + foreach ($product->getStoreIds() as $storeId) { + $bundleOptionCollection = $productRepository->get(self::TEST_PRODUCT_NAME, false, $storeId) + ->getExtensionAttributes()->getBundleProductOptions(); + $this->assertEquals(2, count($bundleOptionCollection)); + $i++; + foreach ($bundleOptionCollection as $optionKey => $option) { + $this->assertEquals('checkbox', $option->getData('type')); + $this->assertEquals('Option ' . $i . ' ' . ($optionKey + 1), $option->getData('title')); + $this->assertEquals(self::TEST_PRODUCT_NAME, $option->getData('sku')); + $this->assertEquals($optionKey + 1, count($option->getData('product_links'))); + foreach ($option->getData('product_links') as $linkKey => $productLink) { + $optionSku = 'Simple ' . ($optionKey + 1 + $linkKey); + $this->assertEquals($optionIdList[$optionSku], $productLink->getData('entity_id')); + $this->assertEquals($optionSku, $productLink->getData('sku')); + } + } + } + } } diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_multiple_store_views.csv new file mode 100644 index 0000000000000..8bedb4ff68167 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/_files/import_bundle_multiple_store_views.csv @@ -0,0 +1,5 @@ +sku,store_view_code,attribute_set_code,product_type,product_websites,name,product_online,price,additional_attributes,qty,out_of_stock_qty,website_id,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values +Simple 1,,Default,simple,base,Simple 1,1,100,,1000,0,1,,,,, +Simple 2,,Default,simple,base,Simple 2,1,200,,1000,0,1,,,,, +Simple 3,,Default,simple,base,Simple 3,1,300,,1000,0,1,,,,, +Bundle 1,,Default,bundle,base,Bundle 1,1,,shipment_type=separately,0,0,1,dynamic,dynamic,Price range,dynamic,"name=Option 1,name_default=Option 1 1,name_fixture_second_store=Option 2 1,type=checkbox,required=1,sku=Simple 1,price=0.0000,default=0,default_qty=1.0000,price_type=fixed|name=Option 2,name_default=Option 1 2,name_fixture_second_store=Option 2 2,type=checkbox,required=1,sku=Simple 2,price=0.0000,default=0,default_qty=1.0000,price_type=fixed|name=Option 2,type=checkbox,required=1,sku=Simple 3,price=0.0000,default=0,default_qty=1.0000,price_type=fixed" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php index befb3ce50473d..e13834428ddf5 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Store\Model\Store; /** * Abstract class for testing product export and import scenarios @@ -111,10 +112,13 @@ protected function executeExportTest($skus, $skippedAttributes) $index = 0; $ids = []; $origProducts = []; + /** @var \Magento\CatalogInventory\Model\StockRegistryStorage $stockRegistryStorage */ + $stockRegistryStorage = $this->objectManager->get(\Magento\CatalogInventory\Model\StockRegistryStorage::class); + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); while (isset($skus[$index])) { $ids[$index] = $this->productResource->getIdBySku($skus[$index]); - $origProducts[$index] = $this->objectManager->create(\Magento\Catalog\Model\Product::class) - ->load($ids[$index]); + $origProducts[$index] = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID); $index++; } @@ -123,9 +127,8 @@ protected function executeExportTest($skus, $skippedAttributes) while ($index > 0) { $index--; - $newProduct = $this->objectManager->create(\Magento\Catalog\Model\Product::class) - ->load($ids[$index]); - + $stockRegistryStorage->removeStockItem($ids[$index]); + $newProduct = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID, true); // @todo uncomment or remove after MAGETWO-49806 resolved //$this->assertEquals(count($origProductData[$index]), count($newProductData)); @@ -294,10 +297,11 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin $index = 0; $ids = []; $origProducts = []; + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); while (isset($skus[$index])) { $ids[$index] = $this->productResource->getIdBySku($skus[$index]); - $origProducts[$index] = $this->objectManager->create(\Magento\Catalog\Model\Product::class) - ->load($ids[$index]); + $origProducts[$index] = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID); $index++; } @@ -317,10 +321,8 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin while ($index > 0) { $index--; - - $id = $this->productResource->getIdBySku($skus[$index]); - $newProduct = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load($id); - + $productRepository->cleanCache(); + $newProduct = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID, true); // check original product is deleted $origProduct = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load($ids[$index]); $this->assertNull($origProduct->getId()); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index bdf894b3a094c..b34cf06c8abc5 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -323,4 +323,90 @@ public function testExportWithMedia() } } } + + /** + * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php + * @return void + */ + public function testExportWithCustomOptions(): void + { + $storeCode = 'default'; + $expectedData = []; + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $store = $this->objectManager->create(\Magento\Store\Model\Store::class); + $store->load('default', 'code'); + /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ + $product = $productRepository->get('simple', 1, $store->getStoreId()); + $newCustomOptions = []; + foreach ($product->getOptions() as $customOption) { + $defaultOptionTitle = $customOption->getTitle(); + $secondStoreOptionTitle = $customOption->getTitle() . '_' . $storeCode; + $expectedData['admin_store'][$defaultOptionTitle] = []; + $expectedData[$storeCode][$secondStoreOptionTitle] = []; + $customOption->setTitle($secondStoreOptionTitle); + if ($customOption->getValues()) { + $newOptionValues = []; + foreach ($customOption->getValues() as $customOptionValue) { + $valueTitle = $customOptionValue->getTitle(); + $expectedData['admin_store'][$defaultOptionTitle][] = $valueTitle; + $expectedData[$storeCode][$secondStoreOptionTitle][] = $valueTitle . '_' . $storeCode; + $newOptionValues[] = $customOptionValue->setTitle($valueTitle . '_' . $storeCode); + } + $customOption->setValues($newOptionValues); + } + $newCustomOptions[] = $customOption; + } + $product->setOptions($newCustomOptions); + $productRepository->save($product); + $this->model->setWriter( + $this->objectManager->create(\Magento\ImportExport\Model\Export\Adapter\Csv::class) + ); + $exportData = $this->model->export(); + /** @var $varDirectory \Magento\Framework\Filesystem\Directory\WriteInterface */ + $varDirectory = $this->objectManager->get(\Magento\Framework\Filesystem::class) + ->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::VAR_DIR); + $varDirectory->writeFile('test_product_with_custom_options_and_second_store.csv', $exportData); + /** @var \Magento\Framework\File\Csv $csv */ + $csv = $this->objectManager->get(\Magento\Framework\File\Csv::class); + $data = $csv->getData($varDirectory->getAbsolutePath('test_product_with_custom_options_and_second_store.csv')); + $customOptionData = []; + foreach ($data[0] as $columnNumber => $columnName) { + if ($columnName === 'custom_options') { + $customOptionData['admin_store'] = $this->parseExportedCustomOption($data[1][$columnNumber]); + $customOptionData[$storeCode] = $this->parseExportedCustomOption($data[2][$columnNumber]); + } + } + + self::assertSame($expectedData, $customOptionData); + } + + /** + * @param $exportedCustomOption + * @return array + */ + private function parseExportedCustomOption($exportedCustomOption) + { + $customOptions = explode('|', $exportedCustomOption); + $optionItems = []; + foreach ($customOptions as $customOption) { + $parsedOptions = array_values( + array_map( + function ($input) { + $data = explode('=', $input); + return [$data[0] => $data[1]]; + }, + explode(',', $customOption) + ) + ); + $optionName = array_column($parsedOptions, 'name')[0]; + if (!empty(array_column($parsedOptions, 'option_title'))) { + $optionItems[$optionName][] = array_column($parsedOptions, 'option_title')[0]; + } else { + $optionItems[$optionName] = []; + } + } + + return $optionItems; + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 5cd28031a997a..63dad7e987186 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -85,11 +85,11 @@ protected function setUp() * @var array */ protected $_assertOptions = [ - 'is_require' => '_custom_option_is_required', - 'price' => '_custom_option_price', - 'sku' => '_custom_option_sku', - 'sort_order' => '_custom_option_sort_order', - 'max_characters' => '_custom_option_max_characters', + 'is_require' => 'required', + 'price' => 'price', + 'sku' => 'sku', + 'sort_order' => 'order', + 'max_characters' => 'max_characters', ]; /** @@ -97,7 +97,23 @@ protected function setUp() * * @var array */ - protected $_assertOptionValues = ['title', 'price', 'sku']; + protected $_assertOptionValues = [ + 'title' => 'option_title', + 'price' => 'price', + 'sku' => 'sku' + ]; + + /** + * List of specific custom option types + * + * @var array + */ + private $specificTypes = [ + 'drop_down', + 'radio', + 'checkbox', + 'multiple', + ]; /** * Test if visibility properly saved after import @@ -319,6 +335,78 @@ public function testSaveCustomOptions($importFile, $sku) $this->assertEquals($customOptionValues, $this->getCustomOptionValues($sku)); } + /** + * Tests adding of custom options with multiple store views + * + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoAppIsolation enabled + */ + public function testSaveCustomOptionsWithMultipleStoreViews() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ + $storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); + $storeCodes = [ + 'admin', + 'default', + 'fixture_second_store' + ]; + /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ + $importFile = 'product_with_custom_options_and_multiple_store_views.csv'; + $sku = 'simple'; + $pathToFile = __DIR__ . '/_files/' . $importFile; + $importModel = $this->createImportModel($pathToFile); + $errors = $importModel->validateData(); + $this->assertTrue($errors->getErrorsCount() == 0); + $importModel->importData(); + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Api\ProductRepositoryInterface::class + ); + foreach ($storeCodes as $storeCode) { + $storeManager->setCurrentStore($storeCode); + $product = $productRepository->get($sku); + $options = $product->getOptionInstance()->getProductOptions($product); + $expectedData = $this->getExpectedOptionsData($pathToFile, $storeCode); + $expectedData = $this->mergeWithExistingData($expectedData, $options); + $actualData = $this->getActualOptionsData($options); + // assert of equal type+titles + $expectedOptions = $expectedData['options']; + // we need to save key values + $actualOptions = $actualData['options']; + sort($expectedOptions); + sort($actualOptions); + $this->assertEquals($expectedOptions, $actualOptions); + + // assert of options data + $this->assertCount(count($expectedData['data']), $actualData['data']); + $this->assertCount(count($expectedData['values']), $actualData['values']); + foreach ($expectedData['options'] as $expectedId => $expectedOption) { + $elementExist = false; + // find value in actual options and values + foreach ($actualData['options'] as $actualId => $actualOption) { + if ($actualOption == $expectedOption) { + $elementExist = true; + $this->assertEquals($expectedData['data'][$expectedId], $actualData['data'][$actualId]); + if (array_key_exists($expectedId, $expectedData['values'])) { + $this->assertEquals($expectedData['values'][$expectedId], $actualData['values'][$actualId]); + } + unset($actualData['options'][$actualId]); + // remove value in case of duplicating key values + break; + } + } + $this->assertTrue($elementExist, 'Element must exist.'); + } + + // Make sure that after importing existing options again, option IDs and option value IDs are not changed + $customOptionValues = $this->getCustomOptionValues($sku); + $this->createImportModel($pathToFile)->importData(); + $this->assertEquals($customOptionValues, $this->getCustomOptionValues($sku)); + } + } + /** * @return array */ @@ -460,9 +548,12 @@ public function testSaveDatetimeAttribute() * Returns expected product data: current id, options, options data and option values * * @param string $pathToFile + * @param string $storeCode * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ - protected function getExpectedOptionsData($pathToFile) + protected function getExpectedOptionsData($pathToFile, $storeCode = ''): array { $productData = $this->csvToArray(file_get_contents($pathToFile)); $expectedOptionId = 0; @@ -471,28 +562,51 @@ protected function getExpectedOptionsData($pathToFile) $expectedData = []; // array of option data $expectedValues = []; - // array of option values data - foreach ($productData['data'] as $data) { - if (!empty($data['_custom_option_type']) && !empty($data['_custom_option_title'])) { - $lastOptionKey = $data['_custom_option_type'] . '|' . $data['_custom_option_title']; - $expectedOptionId++; - $expectedOptions[$expectedOptionId] = $lastOptionKey; - $expectedData[$expectedOptionId] = []; - foreach ($this->_assertOptions as $assertKey => $assertFieldName) { - if (array_key_exists($assertFieldName, $data)) { - $expectedData[$expectedOptionId][$assertKey] = $data[$assertFieldName]; + $storeRowId = null; + foreach ($productData['data'] as $rowId => $rowData) { + $storeCode = ($storeCode == 'admin') ? '' : $storeCode; + if ($rowData['store_view_code'] == $storeCode) { + $storeRowId = $rowId; + break; + } + } + foreach (explode('|', $productData['data'][$storeRowId]['custom_options']) as $optionData) { + $option = array_values( + array_map( + function ($input) { + $data = explode('=', $input); + return [$data[0] => $data[1]]; + }, + explode(',', $optionData) + ) + ); + $option = call_user_func_array('array_merge', $option); + + if (!empty($option['type']) && !empty($option['name'])) { + $lastOptionKey = $option['type'] . '|' . $option['name']; + if (!isset($expectedOptions[$expectedOptionId]) + || $expectedOptions[$expectedOptionId] != $lastOptionKey) { + $expectedOptionId++; + $expectedOptions[$expectedOptionId] = $lastOptionKey; + $expectedData[$expectedOptionId] = []; + foreach ($this->_assertOptions as $assertKey => $assertFieldName) { + if (array_key_exists($assertFieldName, $option) + && !(($assertFieldName == 'price' || $assertFieldName == 'sku') + && in_array($option['type'], $this->specificTypes)) + ) { + $expectedData[$expectedOptionId][$assertKey] = $option[$assertFieldName]; + } } } } - if (!empty($data['_custom_option_row_title']) && empty($data['_custom_option_store'])) { - $optionData = []; - foreach ($this->_assertOptionValues as $assertKey) { - $valueKey = \Magento\CatalogImportExport\Model\Import\Product\Option::COLUMN_PREFIX . - 'row_' . - $assertKey; - $optionData[$assertKey] = $data[$valueKey]; + $optionValue = []; + if (!empty($option['name']) && !empty($option['option_title'])) { + foreach ($this->_assertOptionValues as $assertKey => $assertFieldName) { + if (isset($option[$assertFieldName])) { + $optionValue[$assertKey] = $option[$assertFieldName]; + } } - $expectedValues[$expectedOptionId][] = $optionData; + $expectedValues[$expectedOptionId][] = $optionValue; } } @@ -500,7 +614,7 @@ protected function getExpectedOptionsData($pathToFile) 'id' => $expectedOptionId, 'options' => $expectedOptions, 'data' => $expectedData, - 'values' => $expectedValues + 'values' => $expectedValues, ]; } @@ -521,13 +635,28 @@ protected function mergeWithExistingData( $expectedValues = $expected['values']; foreach ($options as $option) { $optionKey = $option->getType() . '|' . $option->getTitle(); + $optionValues = $this->getOptionValues($option); if (!in_array($optionKey, $expectedOptions)) { $expectedOptionId++; $expectedOptions[$expectedOptionId] = $optionKey; $expectedData[$expectedOptionId] = $this->getOptionData($option); - if ($optionValues = $this->getOptionValues($option)) { + if ($optionValues) { $expectedValues[$expectedOptionId] = $optionValues; } + } else { + $existingOptionId = array_search($optionKey, $expectedOptions); + $expectedData[$existingOptionId] = array_merge( + $this->getOptionData($option), + $expectedData[$existingOptionId] + ); + if ($optionValues) { + foreach ($optionValues as $optionKey => $optionValue) { + $expectedValues[$existingOptionId][$optionKey] = array_merge( + $optionValue, + $expectedValues[$existingOptionId][$optionKey] + ); + } + } } } @@ -603,7 +732,7 @@ protected function getOptionValues(\Magento\Catalog\Model\Product\Option $option /** @var $value \Magento\Catalog\Model\Product\Option\Value */ foreach ($values as $value) { $optionData = []; - foreach ($this->_assertOptionValues as $assertKey) { + foreach (array_keys($this->_assertOptionValues) as $assertKey) { if ($value->hasData($assertKey)) { $optionData[$assertKey] = $value->getData($assertKey); } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options.csv index 2fb3e879a8aed..ac701022a0815 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options.csv @@ -1,2 +1,2 @@ sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled -simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1;sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,price=2,option_title=custom option 1,sku=2-date|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 2,sku=3-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Option 1,sku=4-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Option 2,sku=4-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1,sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,price=2,sku=2-date|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 2,sku=3-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Option 1,sku=4-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Option 2,sku=4-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv new file mode 100644 index 0000000000000..69d460cde472e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv @@ -0,0 +1,4 @@ +sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled +simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 2,sku=3-2-select|name=Test Field Title,type=field,required=1,sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,price=2,sku=2-date|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 1,sku=4-1-select|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 2,sku=4-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 1,sku=5-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 2,sku=5-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_default,type=drop_down,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,option_title=Select Option 2_default|name=Test Field Title_default,type=field|name=Test Date and Time Title_default,type=date_time|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, +simple,,fixture_second_store,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_fixture_second_store,type=drop_down,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,option_title=Select Option 2_fixture_second_store|name=Test Field Title_fixture_second_store,type=field|name=Test Date and Time Title_fixture_second_store,type=date_time|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, From 5e8127a846b3a04920e1d006a7e10a8217bef836 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 25 Apr 2018 10:01:07 +0300 Subject: [PATCH 055/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index 52b6e562a16ab..d2819cb5672aa 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -14,6 +14,11 @@ use Psr\Log\LoggerInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteData; +/** + * Url rewrites DB storage. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class DbStorage extends AbstractStorage { /** From 7a940414f1cd18e39f2e56e6998970d5f392e22d Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 25 Apr 2018 10:16:10 +0300 Subject: [PATCH 056/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- .../UrlRewrite/Model/Storage/DbStorage.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index d2819cb5672aa..f4aa61b145f62 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\UrlRewrite\Model\Storage; use Magento\Framework\Api\DataObjectHelper; @@ -12,7 +13,8 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; use Psr\Log\LoggerInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteData; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; /** * Url rewrites DB storage. @@ -32,7 +34,7 @@ class DbStorage extends AbstractStorage const ERROR_CODE_DUPLICATE_ENTRY = 1062; /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface + * @var AdapterInterface */ protected $connection; @@ -47,9 +49,9 @@ class DbStorage extends AbstractStorage private $logger; /** - * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory + * @param UrlRewriteFactory $urlRewriteFactory * @param DataObjectHelper $dataObjectHelper - * @param \Magento\Framework\App\ResourceConnection $resource + * @param ResourceConnection $resource * @param LoggerInterface|null $logger */ public function __construct( @@ -60,7 +62,7 @@ public function __construct( ) { $this->connection = $resource->getConnection(); $this->resource = $resource; - $this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->logger = $logger ?: ObjectManager::getInstance() ->get(LoggerInterface::class); parent::__construct($urlRewriteFactory, $dataObjectHelper); @@ -70,7 +72,7 @@ public function __construct( * Prepare select statement for specific filter * * @param array $data - * @return \Magento\Framework\DB\Select + * @return Select */ protected function prepareSelect(array $data) { @@ -80,6 +82,7 @@ protected function prepareSelect(array $data) foreach ($data as $column => $value) { $select->where($this->connection->quoteIdentifier($column) . ' IN (?)', $value); } + return $select; } @@ -214,12 +217,12 @@ protected function doReplace(array $urls) foreach ($urls as $url) { $urlFound = $this->doFindOneByData( [ - UrlRewriteData::REQUEST_PATH => $url->getRequestPath(), - UrlRewriteData::STORE_ID => $url->getStoreId(), + UrlRewrite::REQUEST_PATH => $url->getRequestPath(), + UrlRewrite::STORE_ID => $url->getStoreId(), ] ); - if (isset($urlFound[UrlRewriteData::URL_REWRITE_ID])) { - $urlConflicted[$urlFound[UrlRewriteData::URL_REWRITE_ID]] = $url->toArray(); + if (isset($urlFound[UrlRewrite::URL_REWRITE_ID])) { + $urlConflicted[$urlFound[UrlRewrite::URL_REWRITE_ID]] = $url->toArray(); } } if ($urlConflicted) { @@ -281,6 +284,7 @@ protected function createFilterDataBasedOnUrls($urls) } } } + return $data; } From e547972a61efaafec49c8f2eec65a03fba5688ad Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 25 Apr 2018 11:47:00 +0300 Subject: [PATCH 057/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index f4aa61b145f62..ab9082d04f824 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -74,7 +74,7 @@ public function __construct( * @param array $data * @return Select */ - protected function prepareSelect(array $data) + protected function prepareSelect(array $data): Select { $select = $this->connection->select(); $select->from($this->resource->getTableName(self::TABLE_NAME)); From feed4a91165a3836f5ee392358e963cc782a02ef Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 25 Apr 2018 13:22:37 +0300 Subject: [PATCH 058/236] MAGETWO-90331: [Magento Cloud] Custom URL rewrites of a product get deleted in multi store views --- app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index ab9082d04f824..f4aa61b145f62 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -74,7 +74,7 @@ public function __construct( * @param array $data * @return Select */ - protected function prepareSelect(array $data): Select + protected function prepareSelect(array $data) { $select = $this->connection->select(); $select->from($this->resource->getTableName(self::TABLE_NAME)); From 9a85333f21d09eff1f2e150208e46f06a4058317 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Apr 2018 13:44:36 -0500 Subject: [PATCH 059/236] MAGETWO-90055: Refactor category code --- .../Model/Category/DepthCalculator.php | 34 +++++++ .../Model/Category/Hydrator.php | 56 +++++++++++ .../Model/Category/LevelCalculator.php | 44 +++++++++ .../Model/Resolver/Category.php | 2 +- .../Products/DataProvider/CategoryTree.php | 97 +++++-------------- .../CustomAttributesFlattener.php | 4 +- .../Magento/GraphQl/Catalog/CategoryTest.php | 2 +- 7 files changed, 162 insertions(+), 77 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php new file mode 100644 index 0000000000000..baa456c7821ed --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php @@ -0,0 +1,34 @@ +selectionSet->selections ?? []; + $depth = count($selections) ? 1 : 0; + $childrenDepth = [0]; + foreach ($selections as $node) { + $childrenDepth[] = $this->calculate($node); + } + + return $depth + max($childrenDepth); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php new file mode 100644 index 0000000000000..da4ec37c51da4 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php @@ -0,0 +1,56 @@ +flattener = $flattener; + $this->dataObjectProcessor = $dataObjectProcessor; + } + + /** + * Hydrate and flatten category object to flat array + * + * @param CategoryInterface $category + * @return array + */ + public function hydrateCategory(CategoryInterface $category) : array + { + $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); + $categoryData['id'] = $category->getId(); + $categoryData['product_count'] = $category->getProductCount(); + $categoryData['children'] = []; + $categoryData['available_sort_by'] = $category->getAvailableSortBy(); + return $this->flattener->flatten($categoryData); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php new file mode 100644 index 0000000000000..eb57873850b80 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php @@ -0,0 +1,44 @@ +resourceConnection = $resourceConnection; + $this->resourceCategory = $resourceCategory; + } + + /** + * Calculate level data for root category ID specified in GraphQL request + * + * @param int $rootCategoryId + * @return int + */ + public function calculate(int $rootCategoryId) : int + { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from($connection->getTableName('catalog_category_entity'), 'level') + ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); + return (int) $connection->fetchOne($select); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php index 52ecdccecdc3f..a17de7374534b 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php @@ -110,7 +110,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value CategoryInterface::class ); $categories[$item->getId()] = $this->customAttributesFlattener - ->flaternize($categories[$item->getId()]); + ->flatten($categories[$item->getId()]); $categories[$item->getId()]['product_count'] = $item->getProductCount(); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 8b1f8aeb86d49..c5424ad23aa74 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -8,14 +8,14 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; use GraphQL\Language\AST\FieldNode; +use Magento\CatalogGraphQl\Model\Category\DepthCalculator; +use Magento\CatalogGraphQl\Model\Category\LevelCalculator; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Api\Data\CategoryInterface; -use Magento\Catalog\Model\ResourceModel\Category; use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\AttributesJoiner; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Reflection\DataObjectProcessor; /** * Category tree data provider @@ -38,47 +38,40 @@ class CategoryTree private $attributesJoiner; /** - * @var ResourceConnection + * @var DepthCalculator */ - private $resourceConnection; + private $depthCalculator; /** - * @var Category + * @var LevelCalculator */ - private $resourceCategory; + private $levelCalculator; /** - * @var CustomAttributesFlattener + * @var MetadataPool */ - private $customAttributesFlattener; - - /** - * @var DataObjectProcessor - */ - private $dataObjectProcessor; + private $metadata; /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner - * @param ResourceConnection $resourceConnection - * @param Category $resourceCategory - * @param CustomAttributesFlattener $customAttributesFlattener - * @param DataObjectProcessor $dataObjectProcessor + * @param DepthCalculator $depthCalculator + * @param LevelCalculator $levelCalculator + * @param MetadataPool $metadata */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, - ResourceConnection $resourceConnection, - Category $resourceCategory, CustomAttributesFlattener $customAttributesFlattener, - DataObjectProcessor $dataObjectProcessor + DepthCalculator $depthCalculator, + LevelCalculator $levelCalculator, + MetadataPool $metadata ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; - $this->resourceConnection = $resourceConnection; - $this->resourceCategory = $resourceCategory; - $this->customAttributesFlattener = $customAttributesFlattener; - $this->dataObjectProcessor = $dataObjectProcessor; + $this->depthCalculator = $depthCalculator; + $this->levelCalculator = $levelCalculator; + $this->metadata = $metadata; } /** @@ -91,14 +84,17 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $categoryQuery = $resolveInfo->fieldASTs[0]; $collection = $this->collectionFactory->create(); $this->joinAttributesRecursively($collection, $categoryQuery); - $depth = $this->calculateDepth($categoryQuery); - $level = $this->getLevelByRootCategoryId($rootCategoryId); + $depth = $this->depthCalculator->calculate($categoryQuery); + $level = $this->levelCalculator->calculate($rootCategoryId); //Search for desired part of category tree $collection->addPathFilter(sprintf('.*/%s/[/0-9]*$', $rootCategoryId)); $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); - $collection->getSelect()->orWhere($this->resourceCategory->getLinkField() . ' = ?', $rootCategoryId); + $collection->getSelect()->orWhere( + $this->metadata->getMetadata(CategoryInterface::class)->getLinkField() . ' = ?', + $rootCategoryId + ); return $this->processTree($collection->getIterator()); } @@ -123,35 +119,6 @@ private function processTree(\Iterator $iterator) : array return $tree; } - /** - * Hydrate and flatten category object to flat array - * - * @param CategoryInterface $category - * @return array - */ - private function hydrateCategory(CategoryInterface $category) : array - { - $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); - $categoryData['id'] = $category->getId(); - $categoryData['product_count'] = $category->getProductCount(); - $categoryData['children'] = []; - $categoryData['available_sort_by'] = $category->getAvailableSortBy(); - return $this->customAttributesFlattener->flaternize($categoryData); - } - - /** - * @param int $rootCategoryId - * @return int - */ - private function getLevelByRootCategoryId(int $rootCategoryId) : int - { - $connection = $this->resourceConnection->getConnection(); - $select = $connection->select() - ->from($connection->getTableName('catalog_category_entity'), 'level') - ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); - return (int) $connection->fetchOne($select); - } - /** * @param Collection $collection * @param FieldNode $fieldNode @@ -171,20 +138,4 @@ private function joinAttributesRecursively(Collection $collection, FieldNode $fi $this->joinAttributesRecursively($collection, $node); } } - - /** - * @param FieldNode $fieldNode - * @return int - */ - private function calculateDepth(FieldNode $fieldNode) : int - { - $selections = $fieldNode->selectionSet->selections ?? []; - $depth = count($selections) ? 1 : 0; - $childrenDepth = [0]; - foreach ($selections as $node) { - $childrenDepth[] = $this->calculateDepth($node); - } - - return $depth + max($childrenDepth); - } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php index e5dfe760372ff..3f7af2610db1f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CustomAttributesFlattener.php @@ -13,12 +13,12 @@ class CustomAttributesFlattener { /** - * Graphql is waiting for flat array + * Flatten custom attributes within its enclosing array to normalize key-value pairs. * * @param array $categoryData * @return array */ - public function flaternize(array $categoryData) : array + public function flatten(array $categoryData) : array { if (!isset($categoryData['custom_attributes'])) { return $categoryData; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 3dd9ebdd457cb..440d475c80fa8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -97,7 +97,7 @@ public function categorySubtreeTestDataProvider() 'id' => 5, 'name' => 'Category 1.1.1', 'level' => 4, - 'description' => NULL, + 'description' => null, 'path' => '1/2/3/4/5', ], ], From b41713816aa0cd1249543f780da1f064852f7470 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Apr 2018 13:56:00 -0500 Subject: [PATCH 060/236] MAGETWO-90055: Refactor category code --- .../Model/Resolver/Products/DataProvider/CategoryTree.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index c5424ad23aa74..615590fc3a36a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -62,7 +62,6 @@ class CategoryTree public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, - CustomAttributesFlattener $customAttributesFlattener, DepthCalculator $depthCalculator, LevelCalculator $levelCalculator, MetadataPool $metadata From 359a64706cca2e16966fd0cf861c12e48e68d8ac Mon Sep 17 00:00:00 2001 From: Alexander Shkurko Date: Thu, 18 Jan 2018 11:11:09 +0100 Subject: [PATCH 061/236] Recent orders were not filtered per store --- app/code/Magento/Sales/Block/Order/Recent.php | 40 ++++++++++-- .../Test/Unit/Block/Order/RecentTest.php | 64 +++++++++++++------ .../frontend/templates/order/recent.phtml | 9 ++- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Sales/Block/Order/Recent.php b/app/code/Magento/Sales/Block/Order/Recent.php index e57aa1fe420a0..b8d19235731dc 100644 --- a/app/code/Magento/Sales/Block/Order/Recent.php +++ b/app/code/Magento/Sales/Block/Order/Recent.php @@ -5,6 +5,12 @@ */ namespace Magento\Sales\Block\Order; +use Magento\Framework\View\Element\Template\Context; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\Customer\Model\Session; +use Magento\Sales\Model\Order\Config; +use Magento\Store\Model\StoreManagerInterface; + /** * Sales order history block * @@ -13,6 +19,11 @@ */ class Recent extends \Magento\Framework\View\Element\Template { + /** + * Limit of orders + */ + const ORDER_LIMIT = 5; + /** * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory */ @@ -28,23 +39,31 @@ class Recent extends \Magento\Framework\View\Element\Template */ protected $_orderConfig; + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Sales\Model\Order\Config $orderConfig + * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param array $data */ public function __construct( - \Magento\Framework\View\Element\Template\Context $context, - \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory, - \Magento\Customer\Model\Session $customerSession, - \Magento\Sales\Model\Order\Config $orderConfig, + Context $context, + CollectionFactory $orderCollectionFactory, + Session $customerSession, + Config $orderConfig, + StoreManagerInterface $storeManager, array $data = [] ) { $this->_orderCollectionFactory = $orderCollectionFactory; $this->_customerSession = $customerSession; $this->_orderConfig = $orderConfig; + $this->storeManager = $storeManager; parent::__construct($context, $data); $this->_isScopePrivate = true; } @@ -55,11 +74,22 @@ public function __construct( protected function _construct() { parent::_construct(); + $this->getRecentOrders(); + } + + /** + * Get recently placed orders. By default they will be limited by 5. + */ + protected function getRecentOrders() + { $orders = $this->_orderCollectionFactory->create()->addAttributeToSelect( '*' )->addAttributeToFilter( 'customer_id', $this->_customerSession->getCustomerId() + )->addAttributeToFilter( + 'store_id', + $this->storeManager->getStore()->getId() )->addAttributeToFilter( 'status', ['in' => $this->_orderConfig->getVisibleOnFrontStatuses()] @@ -67,7 +97,7 @@ protected function _construct() 'created_at', 'desc' )->setPageSize( - '5' + self::ORDER_LIMIT )->load(); $this->setOrders($orders); } diff --git a/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php b/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php index 99528983a13c9..630263da9a1ed 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php @@ -5,6 +5,15 @@ */ namespace Magento\Sales\Test\Unit\Block\Order; +use Magento\Framework\View\Element\Template\Context; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\Customer\Model\Session; +use Magento\Sales\Model\Order\Config; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\View\Layout; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Sales\Model\ResourceModel\Order\Collection; + class RecentTest extends \PHPUnit\Framework\TestCase { /** @@ -32,26 +41,34 @@ class RecentTest extends \PHPUnit\Framework\TestCase */ protected $orderConfig; + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeManagerMock; + protected function setUp() { - $this->context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); + $this->context = $this->createMock(Context::class); $this->orderCollectionFactory = $this->createPartialMock( - \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class, + CollectionFactory::class, ['create'] ); - $this->customerSession = $this->createPartialMock(\Magento\Customer\Model\Session::class, ['getCustomerId']); + $this->customerSession = $this->createPartialMock(Session::class, ['getCustomerId']); $this->orderConfig = $this->createPartialMock( - \Magento\Sales\Model\Order\Config::class, + Config::class, ['getVisibleOnFrontStatuses'] ); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); } public function testConstructMethod() { $data = []; - $attribute = ['customer_id', 'status']; + $attribute = ['customer_id', 'store_id', 'status']; $customerId = 25; - $layout = $this->createPartialMock(\Magento\Framework\View\Layout::class, ['getBlock']); + $storeId = 4; + $layout = $this->createPartialMock(Layout::class, ['getBlock']); $this->context->expects($this->once()) ->method('getLayout') ->will($this->returnValue($layout)); @@ -64,14 +81,20 @@ public function testConstructMethod() ->method('getVisibleOnFrontStatuses') ->will($this->returnValue($statuses)); - $orderCollection = $this->createPartialMock(\Magento\Sales\Model\ResourceModel\Order\Collection::class, [ - 'addAttributeToSelect', - 'addFieldToFilter', - 'addAttributeToFilter', - 'addAttributeToSort', - 'setPageSize', - 'load' - ]); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $storeMock = $this->getMockBuilder(StoreInterface::class)->getMockForAbstractClass(); + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->any())->method('getId')->willReturn($storeId); + + $orderCollection = $this->createPartialMock(Collection::class, [ + 'addAttributeToSelect', + 'addFieldToFilter', + 'addAttributeToFilter', + 'addAttributeToSort', + 'setPageSize', + 'load' + ]); $this->orderCollectionFactory->expects($this->once()) ->method('create') ->will($this->returnValue($orderCollection)); @@ -85,17 +108,21 @@ public function testConstructMethod() ->willReturnSelf(); $orderCollection->expects($this->at(2)) ->method('addAttributeToFilter') - ->with($attribute[1], $this->equalTo(['in' => $statuses])) - ->will($this->returnSelf()); + ->with($attribute[1], $this->equalTo($storeId)) + ->willReturnSelf(); $orderCollection->expects($this->at(3)) + ->method('addAttributeToFilter') + ->with($attribute[2], $this->equalTo(['in' => $statuses])) + ->will($this->returnSelf()); + $orderCollection->expects($this->at(4)) ->method('addAttributeToSort') ->with('created_at', 'desc') ->will($this->returnSelf()); - $orderCollection->expects($this->at(4)) + $orderCollection->expects($this->at(5)) ->method('setPageSize') ->with('5') ->will($this->returnSelf()); - $orderCollection->expects($this->at(5)) + $orderCollection->expects($this->at(6)) ->method('load') ->will($this->returnSelf()); $this->block = new \Magento\Sales\Block\Order\Recent( @@ -103,6 +130,7 @@ public function testConstructMethod() $this->orderCollectionFactory, $this->customerSession, $this->orderConfig, + $this->storeManagerMock, $data ); $this->assertEquals($orderCollection, $this->block->getOrders()); diff --git a/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml b/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml index df7e6bf334d9a..bb354e920c529 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml @@ -8,10 +8,13 @@ ?>
-getOrders(); ?> +getOrders(); + $count = count($_orders); +?>
- getItems()) > 0): ?> + 0): ?> @@ -19,7 +22,7 @@
getChildHtml() ?> - getItems()) > 0): ?> + 0): ?>
From 8e3b57036c0adbb6512227ea3bff50e1c91fb900 Mon Sep 17 00:00:00 2001 From: Alexander Shkurko Date: Fri, 19 Jan 2018 11:20:13 +0100 Subject: [PATCH 062/236] Backward compatible: adding a constructor parameter --- app/code/Magento/Sales/Block/Order/Recent.php | 10 ++++++---- .../Magento/Sales/Test/Unit/Block/Order/RecentTest.php | 5 ++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Sales/Block/Order/Recent.php b/app/code/Magento/Sales/Block/Order/Recent.php index b8d19235731dc..faf18ae8085fc 100644 --- a/app/code/Magento/Sales/Block/Order/Recent.php +++ b/app/code/Magento/Sales/Block/Order/Recent.php @@ -10,6 +10,7 @@ use Magento\Customer\Model\Session; use Magento\Sales\Model\Order\Config; use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\App\ObjectManager; /** * Sales order history block @@ -57,15 +58,16 @@ public function __construct( CollectionFactory $orderCollectionFactory, Session $customerSession, Config $orderConfig, - StoreManagerInterface $storeManager, - array $data = [] + array $data = [], + StoreManagerInterface $storeManager = null ) { $this->_orderCollectionFactory = $orderCollectionFactory; $this->_customerSession = $customerSession; $this->_orderConfig = $orderConfig; - $this->storeManager = $storeManager; - parent::__construct($context, $data); $this->_isScopePrivate = true; + $this->storeManager = $storeManager ?: ObjectManager::getInstance() + ->get(StoreManagerInterface::class); + parent::__construct($context, $data); } /** diff --git a/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php b/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php index 630263da9a1ed..96162aca42e12 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Order/RecentTest.php @@ -64,7 +64,6 @@ protected function setUp() public function testConstructMethod() { - $data = []; $attribute = ['customer_id', 'store_id', 'status']; $customerId = 25; $storeId = 4; @@ -130,8 +129,8 @@ public function testConstructMethod() $this->orderCollectionFactory, $this->customerSession, $this->orderConfig, - $this->storeManagerMock, - $data + [], + $this->storeManagerMock ); $this->assertEquals($orderCollection, $this->block->getOrders()); } From 732905f100ab7df46aa9ed3e59250e7598ce6a7b Mon Sep 17 00:00:00 2001 From: Alexander Shkurko Date: Tue, 23 Jan 2018 16:00:38 +0100 Subject: [PATCH 063/236] Fix possible error with setting class properties --- app/code/Magento/Sales/Block/Order/Recent.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Block/Order/Recent.php b/app/code/Magento/Sales/Block/Order/Recent.php index faf18ae8085fc..96e889037ad2b 100644 --- a/app/code/Magento/Sales/Block/Order/Recent.php +++ b/app/code/Magento/Sales/Block/Order/Recent.php @@ -50,8 +50,8 @@ class Recent extends \Magento\Framework\View\Element\Template * @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Sales\Model\Order\Config $orderConfig - * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param array $data + * @param \Magento\Store\Model\StoreManagerInterface $storeManager */ public function __construct( Context $context, @@ -65,9 +65,9 @@ public function __construct( $this->_customerSession = $customerSession; $this->_orderConfig = $orderConfig; $this->_isScopePrivate = true; + parent::__construct($context, $data); $this->storeManager = $storeManager ?: ObjectManager::getInstance() ->get(StoreManagerInterface::class); - parent::__construct($context, $data); } /** From fe0759e5b67e02437379ff43f88b8be555d4f866 Mon Sep 17 00:00:00 2001 From: Alexander Shkurko Date: Tue, 23 Jan 2018 16:32:06 +0100 Subject: [PATCH 064/236] Revert changes to fix unit tests --- app/code/Magento/Sales/Block/Order/Recent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Block/Order/Recent.php b/app/code/Magento/Sales/Block/Order/Recent.php index 96e889037ad2b..17436c50bd0e4 100644 --- a/app/code/Magento/Sales/Block/Order/Recent.php +++ b/app/code/Magento/Sales/Block/Order/Recent.php @@ -65,9 +65,9 @@ public function __construct( $this->_customerSession = $customerSession; $this->_orderConfig = $orderConfig; $this->_isScopePrivate = true; - parent::__construct($context, $data); $this->storeManager = $storeManager ?: ObjectManager::getInstance() ->get(StoreManagerInterface::class); + parent::__construct($context, $data); } /** From 193d007f087c8048303e6aa8680f4af5fd1aa272 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 27 Apr 2018 14:36:53 -0500 Subject: [PATCH 065/236] DEVOPS-2174: Fix integration tests --- .../TestFramework/Annotation/DataFixture.php | 11 +++++++++-- .../Annotation/DataFixtureBeforeTransaction.php | 13 +++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php index 6f6f1b9d818af..dc525a46428c4 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixture.php @@ -9,6 +9,8 @@ */ namespace Magento\TestFramework\Annotation; +use PHPUnit\Framework\Exception; + class DataFixture { /** @@ -171,8 +173,13 @@ protected function _applyOneFixture($fixture) require $fixture; } } catch (\Exception $e) { - throw new \Exception( - sprintf("Error in fixture: %s.\n %s", json_encode($fixture), $e->getMessage()), + throw new Exception( + sprintf( + "Error in fixture: %s.\n %s\n %s", + json_encode($fixture), + $e->getMessage(), + $e->getTraceAsString() + ), 500, $e ); diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php index 423bd22f0a8a9..db7f57362d807 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/DataFixtureBeforeTransaction.php @@ -9,6 +9,8 @@ */ namespace Magento\TestFramework\Annotation; +use PHPUnit\Framework\Exception; + class DataFixtureBeforeTransaction { /** @@ -138,8 +140,15 @@ protected function _applyOneFixture($fixture) require $fixture; } } catch (\Exception $e) { - throw new \Exception( - sprintf("Error in fixture: %s.\n %s", json_encode($fixture), (string)$e) + throw new Exception( + sprintf( + "Error in fixture: %s.\n %s\n %s", + json_encode($fixture), + $e->getMessage(), + $e->getTraceAsString() + ), + 500, + $e ); } } From 3015b6476c24ed276465c2f7a53d6631be2af13c Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 27 Apr 2018 16:51:41 -0500 Subject: [PATCH 066/236] DEVOPS-2174: Fix integration tests --- .../Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index 3d717d6282268..d1e88ce527687 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -104,7 +104,7 @@ protected function setUp() $command = str_replace('bin/magento', 'dev/tests/integration/bin/magento', $command); $command = $params . ' ' . $command; - return exec("{$command} > /dev/null &"); + return exec("{$command} >/dev/null 2>&1 &"); }); } From a9a467a33f1ca5738de6defe8274b7a22a20e00f Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Mon, 30 Apr 2018 09:37:54 +0300 Subject: [PATCH 067/236] MAGETWO-90149: Product Import does not allow store-specific Custom Option labels --- .../Model/Export/RowCustomizer.php | 29 ++++++------- .../Model/Import/Product/Type/Bundle.php | 41 +++++++++++-------- .../Model/Import/Product/Option.php | 7 +++- .../Model/Import/Product/Type/BundleTest.php | 2 +- .../AbstractProductExportImportTestCase.php | 1 - .../Model/Export/ProductTest.php | 4 +- .../Model/Import/ProductTest.php | 8 ++-- 7 files changed, 51 insertions(+), 41 deletions(-) diff --git a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php index b805855f363b2..e0c94097e4d3f 100644 --- a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php +++ b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php @@ -10,9 +10,8 @@ use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel; use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection; use Magento\ImportExport\Model\Import as ImportModel; -use \Magento\Catalog\Model\Product\Type\AbstractType; -use \Magento\Framework\App\ObjectManager; -use \Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Store\Model\StoreManagerInterface; /** * Class RowCustomizer @@ -128,7 +127,6 @@ class RowCustomizer implements RowCustomizerInterface /** * @param StoreManagerInterface $storeManager - * @throws \RuntimeException */ public function __construct(StoreManagerInterface $storeManager) { @@ -235,9 +233,9 @@ protected function populateBundleData($collection) * @param \Magento\Catalog\Model\Product $product * @return string */ - protected function getFormattedBundleOptionValues($product) + protected function getFormattedBundleOptionValues(\Magento\Catalog\Model\Product $product): string { - $optionCollections = $this->getProductOptionCollections($product); + $optionCollections = $this->getProductOptionCollection($product); $bundleData = ''; $optionTitles = $this->getBundleOptionTitles($product); foreach ($optionCollections->getItems() as $option) { @@ -298,8 +296,10 @@ function ($value, $key) { * @param string[] $optionTitles * @return string */ - protected function getFormattedOptionValues($option, $optionTitles = []) - { + protected function getFormattedOptionValues( + \Magento\Bundle\Model\Option $option, + array $optionTitles = [] + ): string { $names = implode(ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_map( function ($title, $storeName) { return $storeName . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $title; @@ -433,7 +433,7 @@ private function parseAdditionalAttributes($additionalAttributes) */ private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): array { - $optionCollections = $this->getProductOptionCollections($product); + $optionCollections = $this->getProductOptionCollection($product); $optionsTitles = []; /** @var \Magento\Bundle\Model\Option $option */ foreach ($optionCollections->getItems() as $option) { @@ -442,12 +442,13 @@ private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): $storeIds = $product->getStoreIds(); if (count($storeIds) > 1) { foreach ($storeIds as $storeId) { - $optionCollections = $this->getProductOptionCollections($product, $storeId); + $optionCollections = $this->getProductOptionCollection($product, (int)$storeId); /** @var \Magento\Bundle\Model\Option $option */ foreach ($optionCollections->getItems() as $option) { $optionTitle = $option->getTitle(); if ($optionsTitles[$option->getId()]['name'] != $optionTitle) { - $optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById($storeId)] = $optionTitle; + $optionsTitles[$option->getId()]['name_' . $this->getStoreCodeById((int)$storeId)] = + $optionTitle; } } } @@ -464,9 +465,9 @@ private function getBundleOptionTitles(\Magento\Catalog\Model\Product $product): * @param int $storeId * @return \Magento\Bundle\Model\ResourceModel\Option\Collection */ - private function getProductOptionCollections( + private function getProductOptionCollection( \Magento\Catalog\Model\Product $product, - $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID + int $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID ): \Magento\Bundle\Model\ResourceModel\Option\Collection { $productSku = $product->getSku(); if (!isset($this->optionCollections[$productSku][$storeId])) { @@ -487,7 +488,7 @@ private function getProductOptionCollections( * @param int $storeId * @return string */ - private function getStoreCodeById($storeId): string + private function getStoreCodeById(int $storeId): string { if (!isset($this->storeIdToCode[$storeId])) { $this->storeIdToCode[$storeId] = $this->storeManager->getStore($storeId)->getCode(); diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 6781eda632899..1d2ac3a87ab77 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -8,11 +8,15 @@ */ namespace Magento\BundleImportExport\Model\Import\Product\Type; -use \Magento\Framework\App\ObjectManager; -use \Magento\Bundle\Model\Product\Price as BundlePrice; -use \Magento\Catalog\Model\Product\Type\AbstractType; -use \Magento\CatalogImportExport\Model\Import\Product; -use \Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as AttributeCollectionFactory; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Bundle\Model\Product\Price as BundlePrice; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\CatalogImportExport\Model\Import\Product; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Store\Model\StoreManagerInterface; /** * Class Bundle @@ -148,20 +152,20 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst private $storeCodeToId = []; /** - * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac - * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac - * @param \Magento\Framework\App\ResourceConnection $resource + * @param AttributeSetCollectionFactory $attrSetColFac + * @param AttributeCollectionFactory $prodAttrColFac + * @param ResourceConnection $resource * @param array $params - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool + * @param MetadataPool|null $metadataPool * @param Bundle\RelationsDataSaver|null $relationsDataSaver - * @param StoreManagerInterface $storeManager + * @param StoreManagerInterface|null $storeManager */ public function __construct( - \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac, - \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac, - \Magento\Framework\App\ResourceConnection $resource, + AttributeSetCollectionFactory $attrSetColFac, + AttributeCollectionFactory $prodAttrColFac, + ResourceConnection $resource, array $params, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, + MetadataPool $metadataPool = null, Bundle\RelationsDataSaver $relationsDataSaver = null, StoreManagerInterface $storeManager = null ) { @@ -279,10 +283,10 @@ protected function populateOptionTemplate($option, $entityId, $index = null) * @param int $storeId * @return array */ - protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) + protected function populateOptionValueTemplate(array $option, int $optionId, int $storeId = 0): array { $optionValues = []; - if (isset($option['name']) && isset($option['parent_id']) && $optionId) { + if (isset($option['name'], $option['parent_id']) && $optionId) { $pattern = '/^name[_]?(.*)/'; $keys = array_keys($option); $optionNames = preg_grep($pattern, $keys); @@ -298,6 +302,7 @@ protected function populateOptionValueTemplate($option, $optionId, $storeId = 0) ]; } } + return $optionValues; } @@ -597,7 +602,7 @@ protected function insertOptions() * @param array $optionIds * @return array */ - protected function populateInsertOptionValues($optionIds) + protected function populateInsertOptionValues(array $optionIds): array { $optionValues = []; foreach ($this->_cachedOptions as $entityId => $options) { @@ -616,6 +621,7 @@ protected function populateInsertOptionValues($optionIds) } } } + return $optionValues; } @@ -752,6 +758,7 @@ private function getStoreIdByCode(string $storeCode): int $this->storeCodeToId[$store->getCode()] = $store->getId(); } } + return $this->storeCodeToId[$storeCode]; } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 38ffddd9924cf..f95c2cbf10051 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -760,7 +760,10 @@ protected function _findExistingOptionId(array $newOptionData, array $newOptionT ksort($newOptionTitles); $existingOptions = $this->_oldCustomOptions[$productId]; foreach ($existingOptions as $optionId => $optionData) { - if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'][0] == $newOptionTitles[0]) { + if ( + $optionData['type'] == $newOptionData['type'] + && $optionData['titles'][Store::DEFAULT_STORE_ID] == $newOptionTitles[Store::DEFAULT_STORE_ID] + ) { return $optionId; } } @@ -1158,7 +1161,7 @@ private function processOptionRow($name, $optionRow) * @param array $optionRow * @return array */ - private function addPriceData($result, $optionRow): array + private function addPriceData(array $result, array $optionRow): array { if (isset($optionRow['price'])) { $percent_suffix = ''; diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php index 59ef01d7c581c..29dd6550e4ece 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php @@ -164,7 +164,7 @@ public function testBundleImportWithMultipleStoreViews(): void foreach ($product->getStoreIds() as $storeId) { $bundleOptionCollection = $productRepository->get(self::TEST_PRODUCT_NAME, false, $storeId) ->getExtensionAttributes()->getBundleProductOptions(); - $this->assertEquals(2, count($bundleOptionCollection)); + $this->assertCount(2, $bundleOptionCollection); $i++; foreach ($bundleOptionCollection as $optionKey => $option) { $this->assertEquals('checkbox', $option->getData('type')); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php index e13834428ddf5..b449c94ea4c69 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php @@ -321,7 +321,6 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin while ($index > 0) { $index--; - $productRepository->cleanCache(); $newProduct = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID, true); // check original product is deleted $origProduct = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load($ids[$index]); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index b34cf06c8abc5..70e0cf0e1e74e 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -382,10 +382,10 @@ public function testExportWithCustomOptions(): void } /** - * @param $exportedCustomOption + * @param string $exportedCustomOption * @return array */ - private function parseExportedCustomOption($exportedCustomOption) + private function parseExportedCustomOption(string $exportedCustomOption): array { $customOptions = explode('|', $exportedCustomOption); $optionItems = []; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 63dad7e987186..dcd336e1541b3 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -100,7 +100,7 @@ protected function setUp() protected $_assertOptionValues = [ 'title' => 'option_title', 'price' => 'price', - 'sku' => 'sku' + 'sku' => 'sku', ]; /** @@ -350,7 +350,7 @@ public function testSaveCustomOptionsWithMultipleStoreViews() $storeCodes = [ 'admin', 'default', - 'fixture_second_store' + 'fixture_second_store', ]; /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ $importFile = 'product_with_custom_options_and_multiple_store_views.csv'; @@ -553,7 +553,7 @@ public function testSaveDatetimeAttribute() * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - protected function getExpectedOptionsData($pathToFile, $storeCode = ''): array + protected function getExpectedOptionsData(string $pathToFile, string $storeCode = ''): array { $productData = $this->csvToArray(file_get_contents($pathToFile)); $expectedOptionId = 0; @@ -580,7 +580,7 @@ function ($input) { explode(',', $optionData) ) ); - $option = call_user_func_array('array_merge', $option); + $option = array_merge(...$option); if (!empty($option['type']) && !empty($option['name'])) { $lastOptionKey = $option['type'] . '|' . $option['name']; From fb90bdede1a1071ddeff55e136597380f7e71034 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Mon, 30 Apr 2018 10:02:46 +0300 Subject: [PATCH 068/236] MAGETWO-90149: Product Import does not allow store-specific Custom Option labels --- .../CatalogImportExport/Model/Import/Product/Option.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index f95c2cbf10051..cbaf401f32982 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -760,8 +760,7 @@ protected function _findExistingOptionId(array $newOptionData, array $newOptionT ksort($newOptionTitles); $existingOptions = $this->_oldCustomOptions[$productId]; foreach ($existingOptions as $optionId => $optionData) { - if ( - $optionData['type'] == $newOptionData['type'] + if ($optionData['type'] == $newOptionData['type'] && $optionData['titles'][Store::DEFAULT_STORE_ID] == $newOptionTitles[Store::DEFAULT_STORE_ID] ) { return $optionId; From 7ea06115762dcc4efe9431a67f672692921dc23b Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Mon, 30 Apr 2018 12:11:11 +0300 Subject: [PATCH 069/236] MAGETWO-70886: Zend feed refactoring #9347 --- app/code/Magento/Rss/Model/Rss.php | 8 ++-- .../Controller/Adminhtml/Feed/IndexTest.php | 2 +- .../Test/Unit/Controller/Feed/IndexTest.php | 2 +- .../Magento/Rss/Test/Unit/Model/RssTest.php | 14 ++----- lib/internal/Magento/Framework/App/Feed.php | 39 ++++++------------- .../Magento/Framework/App/FeedFactory.php | 13 ++----- .../Framework/App/FeedFactoryInterface.php | 9 ++--- .../Magento/Framework/App/FeedInterface.php | 11 +----- 8 files changed, 30 insertions(+), 68 deletions(-) diff --git a/app/code/Magento/Rss/Model/Rss.php b/app/code/Magento/Rss/Model/Rss.php index e4aed230ccba4..c46c2240173f6 100644 --- a/app/code/Magento/Rss/Model/Rss.php +++ b/app/code/Magento/Rss/Model/Rss.php @@ -8,9 +8,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Rss\DataProviderInterface; use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\App\FeedInterface; use Magento\Framework\App\FeedFactoryInterface; -use Zend\Feed\Writer\FeedFactory; /** * Provides functionality to work with RSS feeds @@ -100,10 +98,12 @@ public function setDataProvider(DataProviderInterface $dataProvider) /** * @return string + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\RuntimeException */ public function createRssXml() { - $feed = FeedFactory::factory($this->getFeeds()); - return $feed->export('rss'); + $feed = $this->feedFactory->create($this->getFeeds(), FeedFactoryInterface::FORMAT_RSS); + return $feed->getFormattedContent(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 83301ad6b6862..a601f8fb2d1d7 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -119,7 +119,7 @@ public function testExecuteWithException() $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index 9395aac050d63..30415155d5f6e 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -107,7 +107,7 @@ public function testExecuteWithException() $this->rssFactory->expects($this->once())->method('create')->will($this->returnValue($rssModel)); $this->rssManager->expects($this->once())->method('getProvider')->will($this->returnValue($dataProvider)); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\Magento\Framework\Exception\RuntimeException::class); $this->controller->execute(); } } diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index a2995a485abe6..08552bab77d81 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -152,9 +152,8 @@ public function testCreateRssXml() $dataProvider->expects($this->any())->method('getRssData')->will($this->returnValue($this->feedData)); $this->feedMock->expects($this->once()) - ->method('getFormattedContentAs') - ->with(\Magento\Framework\App\FeedInterface::FORMAT_XML) - ->will($this->returnValue($this->feedXml)); + ->method('getFormattedContent') + ->willReturn($this->feedXml); $this->feedFactoryMock->expects($this->once()) ->method('create') @@ -162,13 +161,6 @@ public function testCreateRssXml() ->will($this->returnValue($this->feedMock)); $this->rss->setDataProvider($dataProvider); - $result = $this->rss->createRssXml(); - $this->assertContains('', $result); - $this->assertContains('Feed Title', $result); - $this->assertContains('Feed 1 Title', $result); - $this->assertContains('http://magento.com/rss/link', $result); - $this->assertContains('http://magento.com/rss/link/id/1', $result); - $this->assertContains('Feed Description', $result); - $this->assertContains('', $result); + $this->assertNotNull($this->rss->createRssXml()); } } diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index a76acc2486eb0..d69c6e4df64e0 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -5,47 +5,32 @@ */ namespace Magento\Framework\App; +use Zend\Feed\Writer\FeedFactory; + /** * Default XML feed class */ class Feed implements FeedInterface { - /** - * @var \Zend_Feed - */ - private $feedProcessor; - /** * @var array */ - private $data; + private $feeds; /** - * @param Zend_Feed $feedProcessor - * @param array $data + * Feed constructor. + * @param array $feeds */ - public function __construct( - \Zend_Feed $feedProcessor, - array $data - ) { - $this->feedProcessor = $feedProcessor; - $this->data = $data; + public function __construct(array $feeds) + { + $this->feeds = $feeds; } /** - * Returns the formatted feed content - * - * @param string $format - * - * @return string + * {@inheritdoc} */ - public function getFormattedContentAs( - $format = self::FORMAT_XML - ) { - $feed = $this->feedProcessor::importArray( - $this->data, - FeedFactoryInterface::FORMAT_RSS - ); - return $feed->saveXml(); + public function getFormattedContent() : string + { + return FeedFactory::factory($this->feeds)->export(FeedFactoryInterface::FORMAT_RSS); } } diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 87bd87fdc84c2..9151d7e836bb7 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\App; -use Magento\Framework\App\FeedFactoryInterface; use Magento\Framework\ObjectManagerInterface; use Psr\Log\LoggerInterface; @@ -47,21 +46,17 @@ public function __construct( /** * {@inheritdoc} */ - public function create( - array $data, - $format = FeedFactoryInterface::FORMAT_RSS - ) { + public function create(array $data, $format = FeedFactoryInterface::FORMAT_RSS) : FeedInterface + { if (!isset($this->formats[$format])) { throw new \Magento\Framework\Exception\InputException( - new \Magento\Framework\Phrase('The format is not supported'), - $e + new \Magento\Framework\Phrase('The format is not supported') ); } if (!is_subclass_of($this->formats[$format], \Magento\Framework\App\FeedInterface::class)) { throw new \Magento\Framework\Exception\InputException( - new \Magento\Framework\Phrase('Wrong format handler type'), - $e + new \Magento\Framework\Phrase('Wrong format handler type') ); } diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index b35e3379f9f19..2abaaa61a4b5c 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -20,12 +20,9 @@ interface FeedFactoryInterface * * @throws \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\RuntimeException - * @param array $data - * @param string $format + * @param array $data + * @param string $format * @return FeedInterface */ - public function create( - array $data, - $format = self::FORMAT_RSS - ); + public function create(array $data, $format = self::FORMAT_RSS) : FeedInterface; } diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index 661f8a65e70ac..66515c83b895b 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -11,16 +11,9 @@ interface FeedInterface { /** - * XML feed output format - */ - const FORMAT_XML = 'xml'; - - /** - * @param string $format + * Returns the formatted feed content * * @return string */ - public function getFormattedContentAs( - $format = self::FORMAT_XML - ); + public function getFormattedContent() : string; } From 65dbfb7c777e2c9ccbf45ffec354546e39759a4a Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Mon, 30 Apr 2018 12:43:14 +0000 Subject: [PATCH 070/236] MAGETWO-90388: [FAT] Fix UpgradeSystemTest to correctly work with other components list --- .../Magento/Setup/Test/Block/Readiness.php | 8 ++ .../Setup/Test/Block/SelectVersion.php | 76 ++++++++++++++++++- .../SelectVersion/OtherComponentsGrid.php | 75 ++++++++++++++++++ .../OtherComponentsGrid/Item.php | 49 ++++++++++++ .../AssertVersionAndEditionCheck.php | 21 +++-- .../Setup/Test/TestCase/UpgradeSystemTest.php | 19 ++--- .../Setup/Test/TestCase/UpgradeSystemTest.xml | 7 ++ 7 files changed, 233 insertions(+), 22 deletions(-) create mode 100644 dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php create mode 100644 dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php index 453a7b5940e1d..d48c5f474f26a 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php @@ -192,6 +192,14 @@ public function getDependencyCheck() return $this->_rootElement->find($this->dependencyCheck, Locator::SELECTOR_CSS)->getText(); } + /** + * @return bool + */ + public function isPhpVersionCheckVisible() + { + return $this->_rootElement->find($this->phpVersionCheck)->isVisible(); + } + /** * Get PHP Version check result. * diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php index ea6355d574549..f9f018ad099d9 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php @@ -10,6 +10,7 @@ use Magento\Mtf\Client\Element\SimpleElement; use Magento\Mtf\Client\Locator; use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Setup\Test\Block\SelectVersion\OtherComponentsGrid; /** * Select version block. @@ -37,6 +38,29 @@ class SelectVersion extends Form */ private $showAllVersions = '#showUnstable'; + /** + * CSS selector for Other Components Grid Block. + * + * @var string + */ + private $otherComponentsGrid = '.admin__data-grid-outer-wrap'; + + /** + * @var string + */ + private $empty = '[ng-show="componentsProcessed && total == 0"]'; + + /** + * @var string + */ + private $waitEmpty = + '//div[contains(@ng-show, "componentsProcessed && total") and not(contains(@class,"ng-hide"))]'; + + /** + * @var OtherComponentsGrid + */ + private $otherComponentGrid; + /** * Click on 'Next' button. * @@ -76,13 +100,57 @@ private function chooseShowAllVersions() } /** - * Choose 'yes' for upgrade option called 'Other components' + * Choose 'yes' for upgrade option called 'Other components'. * + * @param array $packages * @return void */ - public function chooseUpgradeOtherComponents() + public function chooseUpgradeOtherComponents(array $packages) { - $this->_rootElement->find("[for=yesUpdateComponents]", Locator::SELECTOR_CSS)->click(); - $this->waitForElementVisible("[ng-show='componentsProcessed']"); + $this->_rootElement->find("[for=yesUpdateComponents]")->click(); + $this->waitForElementNotVisible("[ng-show=\"!componentsProcessed\""); + + if (!$this->isComponentsEmpty()) { + $otherComponentGrid = $this->getOtherComponentsGrid(); + $otherComponentGrid->setItemsPerPage(200); + $otherComponentGrid->setVersions($packages); + } + } + + /** + * Check that grid is empty. + * + * @return bool + */ + public function isComponentsEmpty() + { + $this->waitForElementVisible($this->waitEmpty, Locator::SELECTOR_XPATH); + return $this->_rootElement->find($this->empty)->isVisible(); + } + + /** + * Returns selected packages. + * + * @return array + */ + public function getSelectedPackages() + { + return $this->getOtherComponentsGrid()->getSelectedPackages(); + } + + /** + * Get grid block for other components. + * + * @return OtherComponentsGrid + */ + private function getOtherComponentsGrid() + { + if (!isset($this->otherComponentGrid)) { + $this->otherComponentGrid = $this->blockFactory->create( + OtherComponentsGrid::class, + ['element' => $this->_rootElement->find($this->otherComponentsGrid)] + ); + } + return $this->otherComponentGrid; } } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php new file mode 100644 index 0000000000000..64588f6df9c0e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php @@ -0,0 +1,75 @@ +itemComponent, $package['name']); + $elements = $this->_rootElement->getElements($selector, Locator::SELECTOR_XPATH); + foreach ($elements as $element) { + $row = $this->getComponentRow($element); + $row->setVersion($package['version']); + $this->selectedPackages[$row->getPackageName()] = $package['version']; + } + } + } + + /** + * Returns selected packages. + * + * @return array + */ + public function getSelectedPackages() + { + return $this->selectedPackages; + } + + /** + * @param int $count + */ + public function setItemsPerPage($count) + { + $this->_rootElement->find($this->perPage, Locator::SELECTOR_CSS, 'select')->setValue($count); + } + + /** + * @param ElementInterface $element + * @return Item + */ + private function getComponentRow($element) + { + return $this->blockFactory->create( + Item::class, + ['element' => $element] + ); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php new file mode 100644 index 0000000000000..8eded2b3d9922 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php @@ -0,0 +1,49 @@ +_rootElement->find($this->version, Locator::SELECTOR_CSS, 'select')->setValue($version); + } + + /** + * Returns package name of element. + * + * @return array|string + */ + public function getPackageName() + { + return $this->_rootElement->find($this->packageName)->getText(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php index 5ddc5794ff816..057147586734e 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php @@ -18,17 +18,24 @@ class AssertVersionAndEditionCheck extends AbstractConstraint * Assert that package and version is correct * * @param SetupWizard $setupWizard - * @param string $package - * @param string $version + * @param array $upgrade * @return void */ - public function processAssert(SetupWizard $setupWizard, $package, $version) + public function processAssert(SetupWizard $setupWizard, array $upgrade) { - $message = "We're ready to upgrade $package to $version"; - \PHPUnit\Framework\Assert::assertContains( + $message = "We're ready to upgrade {$upgrade['package']} to {$upgrade['version']}."; + if ($upgrade['otherComponents'] === 'Yes' && isset($upgrade['selectedPackages'])) { + foreach ($upgrade['selectedPackages'] as $name => $version) { + $message .= "\nWe're ready to upgrade {$name} to {$version}."; + } + } + $actualMessage = $setupWizard->getSystemUpgrade()->getUpgradeMessage(); + \PHPUnit_Framework_Assert::assertContains( $message, - $setupWizard->getSystemUpgrade()->getUpgradeMessage(), - 'Updater application check is incorrect.' + $actualMessage, + "Updater application check is incorrect: \n" + . "Expected: '$message' \n" + . "Actual: '$actualMessage'" ); } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php index 53c36e0a1e1b0..c9b3ce35e84ed 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php @@ -31,23 +31,18 @@ class UpgradeSystemTest extends Injectable protected $adminDashboard; /** - * @var \Magento\Mtf\Util\Iterator\ApplicationState - */ - private $applicationStateIterator; - - /** + * Injection data. + * * @param Dashboard $adminDashboard * @param SetupWizard $setupWizard - * @param \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator + * @return void */ public function __inject( Dashboard $adminDashboard, - SetupWizard $setupWizard, - \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator + SetupWizard $setupWizard ) { $this->adminDashboard = $adminDashboard; $this->setupWizard = $setupWizard; - $this->applicationStateIterator = $applicationStateIterator; } /** @@ -114,7 +109,7 @@ public function test( $this->setupWizard->getSetupHome()->clickSystemUpgrade(); $this->setupWizard->getSelectVersion()->fill($upgradeFixture); if ($upgrade['otherComponents'] === 'Yes') { - $this->setupWizard->getSelectVersion()->chooseUpgradeOtherComponents(); + $this->setupWizard->getSelectVersion()->chooseUpgradeOtherComponents($upgrade['otherComponentsList']); } $this->setupWizard->getSelectVersion()->clickNext(); @@ -128,7 +123,9 @@ public function test( $this->setupWizard->getCreateBackup()->clickNext(); // Check info and press 'Upgrade' button - $assertVersionAndEdition->processAssert($this->setupWizard, $upgrade['package'], $version); + $upgrade['version'] = $version; + $upgrade['selectedPackages'] = $this->setupWizard->getSelectVersion()->getSelectedPackages(); + $assertVersionAndEdition->processAssert($this->setupWizard, $upgrade); $this->setupWizard->getSystemUpgrade()->clickSystemUpgrade(); $assertSuccessMessage->processAssert($this->setupWizard, $upgrade['package']); diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml index b092fe1812201..95193dbf6c097 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.xml @@ -18,6 +18,13 @@ No No {otherComponents} + + + + {package_0_name} + {package_0_version} + + From 2bb9b695d7f610c0fa0b77aa17d024b29a0b0061 Mon Sep 17 00:00:00 2001 From: rostyslav-hymon Date: Mon, 30 Apr 2018 15:49:55 +0300 Subject: [PATCH 071/236] change function getRecentOrders() to private. --- app/code/Magento/Sales/Block/Order/Recent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Block/Order/Recent.php b/app/code/Magento/Sales/Block/Order/Recent.php index 17436c50bd0e4..7e5be0ebfbba2 100644 --- a/app/code/Magento/Sales/Block/Order/Recent.php +++ b/app/code/Magento/Sales/Block/Order/Recent.php @@ -82,7 +82,7 @@ protected function _construct() /** * Get recently placed orders. By default they will be limited by 5. */ - protected function getRecentOrders() + private function getRecentOrders() { $orders = $this->_orderCollectionFactory->create()->addAttributeToSelect( '*' From db09833c892542ae6f301c56a854c5d0461361c1 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Mon, 30 Apr 2018 16:10:14 +0300 Subject: [PATCH 072/236] MAGETWO-90286: Bundle Product is_salable check is wrong --- .../Magento/Bundle/Model/Product/Type.php | 10 +++-- .../ResourceModel/Selection/Collection.php | 18 +++++++-- .../Selection/CollectionTest.php | 38 ++++++++++++++---- .../Bundle/_files/multiple_products.php | 40 ++++++++++++++++--- 4 files changed, 87 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index d377e03ae9594..f5e05cbc3e212 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -12,6 +12,7 @@ use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\EntityManager\MetadataPool; use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier; +use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections; /** * Bundle Type Model @@ -484,7 +485,9 @@ public function getSelectionsCollection($optionIds, $product) \Magento\Catalog\Api\Data\ProductInterface::class ); - $selectionsCollection = $this->_bundleCollection->create() + /** @var Selections $selectionsCollection */ + $selectionsCollection = $this->_bundleCollection->create(); + $selectionsCollection ->addAttributeToSelect($this->_config->getProductAttributes()) ->addAttributeToSelect('tax_class_id') //used for calculation item taxes in Bundle with Dynamic Price ->setFlag('product_children', true) @@ -853,8 +856,9 @@ public function getSelectionsByIds($selectionIds, $product) if (!$usedSelections || $usedSelectionsIds !== $selectionIds) { $storeId = $product->getStoreId(); - $usedSelections = $this->_bundleCollection - ->create() + /** @var Selections $usedSelections */ + $usedSelections = $this->_bundleCollection->create(); + $usedSelections ->addAttributeToSelect('*') ->setFlag('product_children', true) ->addStoreFilter($this->getStoreFilter($product)) diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index 0216812199b50..aedb6853a1ca0 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -163,21 +163,33 @@ public function setPositionOrder() } /** - * Add filtering of product then havent enoght stock + * Add filtering of products that have 0 items left. * * @return $this * @since 100.2.0 */ public function addQuantityFilter() { + $stockItemTable = $this->getTable('cataloginventory_stock_item'); + $stockStatusTable = $this->getTable('cataloginventory_stock_status'); $this->getSelect() ->joinInner( - ['stock' => $this->getTable('cataloginventory_stock_status')], + ['stock' => $stockStatusTable], 'selection.product_id = stock.product_id', [] + )->joinInner( + ['stock_item' => $stockItemTable], + 'selection.product_id = stock_item.product_id', + [] ) ->where( - '(selection.selection_can_change_qty or selection.selection_qty <= stock.qty) and stock.stock_status' + '(' + . 'selection.selection_can_change_qty' + . ' or ' + . 'selection.selection_qty <= stock.qty' + . ' or ' + .'stock_item.manage_stock = 0' + . ') and stock.stock_status = 1' ); return $this; } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php b/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php index cbe34639e8267..cf73a3902c1b2 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php @@ -111,17 +111,39 @@ protected function setUp() public function testAddQuantityFilter() { - $tableName = 'cataloginventory_stock_status'; - $this->entity->expects($this->once()) + $statusTableName = 'cataloginventory_stock_status'; + $itemTableName = 'cataloginventory_stock_item'; + $this->entity->expects($this->exactly(2)) ->method('getTable') - ->willReturn($tableName); - $this->select->expects($this->once()) + ->willReturnOnConsecutiveCalls($itemTableName, $statusTableName); + $this->select->expects($this->exactly(2)) ->method('joinInner') - ->with( - ['stock' => $tableName], - 'selection.product_id = stock.product_id', - [] + ->withConsecutive( + [ + ['stock' => $statusTableName], + 'selection.product_id = stock.product_id', + [], + ], + [ + ['stock_item' => $itemTableName], + 'selection.product_id = stock_item.product_id', + [], + ] )->willReturnSelf(); + $this->select + ->expects($this->once()) + ->method('where') + ->with( + '(' + . 'selection.selection_can_change_qty' + . ' or ' + . 'selection.selection_qty <= stock.qty' + . ' or ' + .'stock_item.manage_stock = 0' + . ') and stock.stock_status = 1' + ) + ->willReturnSelf(); + $this->assertEquals($this->model, $this->model->addQuantityFilter()); } } diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php index a70075934c55f..52abd7d6ff8eb 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php @@ -29,7 +29,13 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setCateroryIds([]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ]); $productRepository->save($product); @@ -54,7 +60,13 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setCateroryIds([]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 50, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ]); $productRepository->save($product); @@ -74,7 +86,13 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setCateroryIds([]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 140, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ]); $productRepository->save($product); @@ -99,7 +117,13 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setCateroryIds([]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 20, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 20, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ]); $productRepository->save($product); @@ -124,6 +148,12 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setCateroryIds([]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 15, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 15, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ]); $productRepository->save($product); From ca8256e48818a2cd27967e2985a9e7ff3d74d849 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Mon, 30 Apr 2018 13:46:14 +0000 Subject: [PATCH 073/236] MAGETWO-90388: [FAT] Fix UpgradeSystemTest to correctly work with other components list --- .../Setup/Test/Block/SelectVersion/OtherComponentsGrid.php | 2 ++ .../Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php index 64588f6df9c0e..7b34167c0401f 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Setup\Test\Block\SelectVersion; use Magento\Mtf\Block\Block; diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php index 8eded2b3d9922..576eccb487f82 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Setup\Test\Block\SelectVersion\OtherComponentsGrid; use Magento\Mtf\Block\Block; From 3e1ef924aac268e6d8dae5a0a944396002a4520b Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Tue, 1 May 2018 10:11:01 -0500 Subject: [PATCH 074/236] MAGETWO-90241: Image Uploader maxFileSize configuration can exceed PHP's upload_max_filesize Add ImageTest coverage for maxFileSize fix --- .../Form/Element/DataType/Media/ImageTest.php | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php new file mode 100644 index 0000000000000..ebe4d10475cc9 --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/Media/ImageTest.php @@ -0,0 +1,127 @@ +processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->context->expects($this->atLeastOnce())->method('getProcessor')->willReturn($this->processor); + + $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); + + $this->store = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->store->expects($this->any())->method('getId')->willReturn(0); + + $this->storeManager->expects($this->any())->method('getStore')->willReturn($this->store); + + $this->fileSize = $this->getMockBuilder(Size::class)->getMock(); + + $this->objectManager = new ObjectManager($this); + + $this->image = $this->objectManager->getObject(Image::class, [ + 'context' => $this->context, + 'storeManager' => $this->storeManager, + 'fileSize' => $this->fileSize + ]); + + $this->image->setData([ + 'config' => [ + 'initialMediaGalleryOpenSubpath' => 'open/sesame', + ], + ]); + } + + /** + * @dataProvider prepareDataProvider + */ + public function testPrepare() + { + $this->assertExpectedPreparedConfiguration(...func_get_args()); + } + + /** + * Data provider for testPrepare + * @return array + */ + public function prepareDataProvider(): array + { + return [ + [['maxFileSize' => 10], 10, ['maxFileSize' => 10]], + [['maxFileSize' => null], 10, ['maxFileSize' => 10]], + [['maxFileSize' => 10], 5, ['maxFileSize' => 5]], + [['maxFileSize' => 10], 20, ['maxFileSize' => 10]], + [['maxFileSize' => 0], 10, ['maxFileSize' => 10]], + ]; + } + + /** + * @param array $initialConfig + * @param int $maxFileSizeSupported + * @param array $expectedPreparedConfig + */ + private function assertExpectedPreparedConfiguration( + array $initialConfig, + int $maxFileSizeSupported, + array $expectedPreparedConfig + ) { + $this->image->setData(array_merge_recursive(['config' => $initialConfig], $this->image->getData())); + + $this->fileSize->expects($this->any())->method('getMaxFileSize')->willReturn($maxFileSizeSupported); + + $this->image->prepare(); + + $actualRelevantPreparedConfig = array_intersect_key($this->image->getConfiguration(), $initialConfig); + + $this->assertEquals( + $expectedPreparedConfig, + $actualRelevantPreparedConfig + ); + } +} From 3439f737b3352a617e8c03fe7d04be68a3d1d608 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Tue, 1 May 2018 10:43:17 -0500 Subject: [PATCH 075/236] MAGETWO-90241: Image Uploader maxFileSize configuration can exceed PHP's upload_max_filesize Null coalesce to zero if maxFileSize is null --- .../Magento/Ui/Component/Form/Element/DataType/Media/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php index cab96d796c63e..cbce94c72382a 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php @@ -65,7 +65,7 @@ public function prepare() { // dynamically set max file size based on php ini config if not present in XML $maxFileSize = min(array_filter([ - $this->getConfiguration()['maxFileSize'] ?? null, + $this->getConfiguration()['maxFileSize'] ?? 0, $this->fileSize->getMaxFileSize() ])); From 3bed8a97ecda01df2ae2d0e308647f11ed2135f7 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Tue, 1 May 2018 13:24:19 -0500 Subject: [PATCH 076/236] MAGETWO-91003: Border Color should be disabled when "Border" is selected None Add enable/disable state in update binding and instantiate with same config regardless of initial state --- .../js/lib/knockout/bindings/color-picker.js | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/color-picker.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/color-picker.js index f8b7f2d46a296..c678b85276093 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/color-picker.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/color-picker.js @@ -11,6 +11,16 @@ define([ ], function (ko, $, renderer, spectrum, tinycolor) { 'use strict'; + /** + * Change color picker status to be enabled or disabled + * + * @param {HTMLElement} element - Element to apply colorpicker enable/disable status to. + * @param {Object} viewModel - Object, which represents view model binded to el. + */ + function changeColorPickerStateBasedOnViewModel(element, viewModel) { + $(element).spectrum(viewModel.disabled() ? 'disable' : 'enable'); + } + ko.bindingHandlers.colorPicker = { /** * Binding init callback. @@ -31,35 +41,34 @@ define([ config.value(value.toString()); }; - if (!viewModel.disabled()) { - config.change = changeValue; + config.change = changeValue; - config.hide = changeValue; + config.hide = changeValue; - /** show value */ - config.show = function () { - if (!viewModel.focused()) { - viewModel.focused(true); - } + /** show value */ + config.show = function () { + if (!viewModel.focused()) { + viewModel.focused(true); + } - return true; - }; - $(element).spectrum(config); - } else { - $(element).spectrum({ - disabled: true - }); - } + return true; + }; + + $(element).spectrum(config); + + changeColorPickerStateBasedOnViewModel(element, viewModel); }, /** * Reads params passed to binding, parses component declarations. * Fetches for those found and attaches them to the new context. * - * @param {HTMLElement} element - * @param {Function} valueAccessor + * @param {HTMLElement} element - Element to apply bindings to. + * @param {Function} valueAccessor - Function that returns value, passed to binding. + * @param {Object} allBindings - Object, which represents all bindings applied to element. + * @param {Object} viewModel - Object, which represents view model binded to element. */ - update: function (element, valueAccessor) { + update: function (element, valueAccessor, allBindings, viewModel) { var config = valueAccessor(); if (tinycolor(config.value()).isValid() || config.value() === '') { @@ -69,6 +78,8 @@ define([ config.value($(element).spectrum('get').toString()); } } + + changeColorPickerStateBasedOnViewModel(element, viewModel); } }; From af1b53417ced397a96a5609cf3a6122b5d0f6519 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Tue, 1 May 2018 13:58:28 -0500 Subject: [PATCH 077/236] MAGETWO-91003: Border Color should be disabled when "Border" is selected None Refactor colorpicker KO binding unit test --- .../base/js/lib/ko/bind/color-picker.test.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/color-picker.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/color-picker.test.js index a2f338c45b5a6..9b756a2f2a49a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/color-picker.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/color-picker.test.js @@ -29,7 +29,10 @@ define([ describe('Colorpicker binding', function () { it('Should call spectrum on $input with disabled configuration if view model disabled', function () { - var valueAccessor = jasmine.createSpy().and.returnValue({}), + var value = { + configStuffInHere: true + }, + valueAccessor = jasmine.createSpy().and.returnValue(value), viewModel = { disabled: jasmine.createSpy().and.returnValue(true) }; @@ -39,9 +42,13 @@ define([ ko.bindingHandlers.colorPicker.init($input, valueAccessor, null, viewModel); - expect($.fn.spectrum).toHaveBeenCalledWith({ - disabled: true - }); + expect(value.change).toEqual(jasmine.any(Function)); + expect(value.hide).toEqual(jasmine.any(Function)); + expect(value.show).toEqual(jasmine.any(Function)); + expect(value.change).toBe(value.hide); + + expect($.fn.spectrum.calls.allArgs()).toEqual([[value], ['disable']]); + expect(viewModel.disabled).toHaveBeenCalled(); $.fn.init = jasmine.createSpy().and.returnValue($.fn); @@ -69,7 +76,8 @@ define([ expect(value.show).toEqual(jasmine.any(Function)); expect(value.change).toBe(value.hide); - expect($.fn.spectrum).toHaveBeenCalledWith(value); + expect($.fn.spectrum.calls.allArgs()).toEqual([[value], ['enable']]); + expect(viewModel.disabled).toHaveBeenCalled(); $.fn.init = jasmine.createSpy().and.returnValue($.fn); From 74778c664f2dad9db52bf64af95f4675708117de Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Tue, 1 May 2018 15:27:08 -0500 Subject: [PATCH 078/236] DEVOPS-2174: Fix integration tests --- .../Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index d1e88ce527687..19b0f06c31784 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -104,6 +104,7 @@ protected function setUp() $command = str_replace('bin/magento', 'dev/tests/integration/bin/magento', $command); $command = $params . ' ' . $command; + // Added 2>&1 workaround to hide error messages until MAGETWO-90176 is fixed return exec("{$command} >/dev/null 2>&1 &"); }); } From 91d137c055c743fb5d9d96dc38885b8535e25c19 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Wed, 2 May 2018 12:42:14 +0300 Subject: [PATCH 079/236] MAGETWO-90286: Bundle Product is_salable check is wrong --- .../ResourceModel/Selection/Collection.php | 8 +++-- .../Selection/CollectionTest.php | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index aedb6853a1ca0..e9295b22674bd 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -184,13 +184,15 @@ public function addQuantityFilter() ) ->where( '(' - . 'selection.selection_can_change_qty' + . 'selection.selection_can_change_qty > 0' . ' or ' . 'selection.selection_qty <= stock.qty' . ' or ' .'stock_item.manage_stock = 0' - . ') and stock.stock_status = 1' - ); + . ')' + ) + ->where('stock.stock_status = 1'); + return $this; } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php b/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php index cf73a3902c1b2..e595f9a47f060 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php @@ -115,7 +115,10 @@ public function testAddQuantityFilter() $itemTableName = 'cataloginventory_stock_item'; $this->entity->expects($this->exactly(2)) ->method('getTable') - ->willReturnOnConsecutiveCalls($itemTableName, $statusTableName); + ->willReturnMap([ + ['cataloginventory_stock_item', $itemTableName], + ['cataloginventory_stock_status', $statusTableName], + ]); $this->select->expects($this->exactly(2)) ->method('joinInner') ->withConsecutive( @@ -131,18 +134,22 @@ public function testAddQuantityFilter() ] )->willReturnSelf(); $this->select - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('where') - ->with( - '(' - . 'selection.selection_can_change_qty' - . ' or ' - . 'selection.selection_qty <= stock.qty' - . ' or ' - .'stock_item.manage_stock = 0' - . ') and stock.stock_status = 1' - ) - ->willReturnSelf(); + ->withConsecutive( + [ + '(' + . 'selection.selection_can_change_qty > 0' + . ' or ' + . 'selection.selection_qty <= stock.qty' + . ' or ' + .'stock_item.manage_stock = 0' + . ')', + ], + [ + 'stock.stock_status = 1', + ] + )->willReturnSelf(); $this->assertEquals($this->model, $this->model->addQuantityFilter()); } From 32a9110fde968459a27622d87f0db65001f7438d Mon Sep 17 00:00:00 2001 From: Stas Kozar Date: Wed, 2 May 2018 12:43:17 +0300 Subject: [PATCH 080/236] MAGETWO-90310: [Magento Cloud] - PayPal pop up does not work with virtual product (gift card) --- .../Payment/Model/Method/AbstractMethod.php | 15 +- .../Express/AbstractExpress/PlaceOrder.php | 23 +- .../Magento/Paypal/Model/Express/Checkout.php | 7 +- .../Paypal/Model/Express/CheckoutTest.php | 198 +++++++++++++----- ...rtual_quote_with_empty_billing_address.php | 76 +++++++ 5 files changed, 263 insertions(+), 56 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php diff --git a/app/code/Magento/Payment/Model/Method/AbstractMethod.php b/app/code/Magento/Payment/Model/Method/AbstractMethod.php index 6e6a6773bd5f2..33200014c7ec1 100644 --- a/app/code/Magento/Payment/Model/Method/AbstractMethod.php +++ b/app/code/Magento/Payment/Model/Method/AbstractMethod.php @@ -6,12 +6,14 @@ namespace Magento\Payment\Model\Method; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Payment\Model\InfoInterface; use Magento\Payment\Model\MethodInterface; use Magento\Payment\Observer\AbstractDataAssignObserver; use Magento\Quote\Api\Data\PaymentMethodInterface; use Magento\Sales\Model\Order\Payment; +use Magento\Directory\Helper\Data as DirectoryHelper; /** * Payment method abstract model @@ -194,6 +196,11 @@ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibl */ protected $logger; + /** + * @var DirectoryHelper + */ + private $directory; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -205,6 +212,7 @@ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibl * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param DirectoryHelper $directory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -217,7 +225,8 @@ public function __construct( \Magento\Payment\Model\Method\Logger $logger, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + DirectoryHelper $directory = null ) { parent::__construct( $context, @@ -231,6 +240,7 @@ public function __construct( $this->_paymentData = $paymentData; $this->_scopeConfig = $scopeConfig; $this->logger = $logger; + $this->directory = $directory ?: ObjectManager::getInstance()->get(DirectoryHelper::class); $this->initializeData($data); } @@ -584,11 +594,14 @@ public function validate() } else { $billingCountry = $paymentInfo->getQuote()->getBillingAddress()->getCountryId(); } + $billingCountry = $billingCountry ?: $this->directory->getDefaultCountry(); + if (!$this->canUseForCountry($billingCountry)) { throw new \Magento\Framework\Exception\LocalizedException( __('You can\'t use the payment type you selected to make payments to the billing country.') ); } + return $this; } diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php index eb4c35b02696c..f0fce97da512a 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php @@ -7,6 +7,7 @@ namespace Magento\Paypal\Controller\Express\AbstractExpress; +use Magento\Framework\Exception\LocalizedException; use Magento\Paypal\Model\Api\ProcessableException as ApiProcessableException; /** @@ -118,15 +119,27 @@ public function execute() return; } catch (ApiProcessableException $e) { $this->_processPaypalApiError($e); + } catch (LocalizedException $e) { + $this->processException($e, $e->getRawMessage()); } catch (\Exception $e) { - $this->messageManager->addExceptionMessage( - $e, - __('We can\'t place the order.') - ); - $this->_redirect('*/*/review'); + $this->processException($e, 'We can\'t place the order.'); } } + /** + * Process exception. + * + * @param \Exception $exception + * @param string $message + * + * @return void + */ + private function processException(\Exception $exception, string $message): void + { + $this->messageManager->addExceptionMessage($exception, __($message)); + $this->_redirect('*/*/review'); + } + /** * Process PayPal API's processable errors * diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index e9f2c1b8415a8..649c653ef1a65 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -897,7 +897,12 @@ protected function _setExportedAddressData($address, $exportedAddress) { // Exported data is more priority if we came from Express Checkout button $isButton = (bool)$this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON); - if (!$isButton) { + + // Since country is required field for billing and shipping address, + // we consider the address information to be empty if country is empty. + $isEmptyAddress = ($address->getCountryId() === null); + + if (!$isButton && !$isEmptyAddress) { return; } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index b1ba88f601cdd..982d5857d4221 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -12,6 +12,7 @@ use Magento\Paypal\Model\Config; use Magento\Paypal\Model\Info; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\ResourceModel\Quote\Collection; use Magento\TestFramework\Helper\Bootstrap; @@ -28,22 +29,22 @@ class CheckoutTest extends \PHPUnit\Framework\TestCase private $objectManager; /** - * @var Info + * @var Info|\PHPUnit_Framework_MockObject_MockObject */ private $paypalInfo; /** - * @var Config + * @var Config|\PHPUnit_Framework_MockObject_MockObject */ private $paypalConfig; /** - * @var Factory + * @var Factory|\PHPUnit_Framework_MockObject_MockObject */ private $apiTypeFactory; /** - * @var Nvp + * @var Nvp|\PHPUnit_Framework_MockObject_MockObject */ private $api; @@ -96,7 +97,7 @@ protected function setUp() */ public function testCheckoutStartWithBillingAddress() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $paypalConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); @@ -151,7 +152,7 @@ public function testCheckoutStartWithBillingAddress() public function testPrepareCustomerQuote() { /** @var Quote $quote */ - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $quote->setCheckoutMethod(Onepage::METHOD_CUSTOMER); // to dive into _prepareCustomerQuote() on switch $quote->getShippingAddress()->setSameAsBilling(0); $quote->setReservedOrderId(null); @@ -163,7 +164,7 @@ public function testPrepareCustomerQuote() /** @var \Magento\Customer\Model\Session $customerSession */ $customerSession = $this->objectManager->get(\Magento\Customer\Model\Session::class); $customerSession->loginById(1); - $checkout = $this->_getCheckout($quote); + $checkout = $this->getCheckout($quote); $checkout->place('token'); /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerService */ @@ -191,12 +192,12 @@ public function testPrepareCustomerQuote() public function testPlaceGuestQuote() { /** @var Quote $quote */ - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $quote->setCheckoutMethod(Onepage::METHOD_GUEST); // to dive into _prepareGuestQuote() on switch $quote->getShippingAddress()->setSameAsBilling(0); $quote->setReservedOrderId(null); - $checkout = $this->_getCheckout($quote); + $checkout = $this->getCheckout($quote); $checkout->place('token'); $this->assertNull($quote->getCustomerId()); @@ -218,7 +219,7 @@ public function testPlaceGuestQuote() * @param Quote $quote * @return Checkout */ - protected function _getCheckout(Quote $quote) + protected function getCheckout(Quote $quote) { return $this->objectManager->create( Checkout::class, @@ -242,7 +243,7 @@ protected function _getCheckout(Quote $quote) */ public function testReturnFromPaypal() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->checkoutModel = $this->objectManager->create( Checkout::class, [ @@ -252,12 +253,13 @@ public function testReturnFromPaypal() ] ); - $exportedBillingAddress = $this->_getExportedAddressFixture($quote->getBillingAddress()->getData()); + $prefix = 'exported'; + $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData(), $prefix); $this->api->expects($this->any()) ->method('getExportedBillingAddress') ->will($this->returnValue($exportedBillingAddress)); - $exportedShippingAddress = $this->_getExportedAddressFixture($quote->getShippingAddress()->getData()); + $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData(), $prefix); $this->api->expects($this->any()) ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); @@ -269,7 +271,7 @@ public function testReturnFromPaypal() $this->checkoutModel->returnFromPaypal('token'); $billingAddress = $quote->getBillingAddress(); - $this->assertContains('exported', $billingAddress->getFirstname()); + $this->assertContains($prefix, $billingAddress->getFirstname()); $this->assertEquals('note', $billingAddress->getCustomerNote()); $shippingAddress = $quote->getShippingAddress(); @@ -298,27 +300,25 @@ public function testReturnFromPaypal() */ public function testReturnFromPaypalButton() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->prepareCheckoutModel($quote); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 1); $this->checkoutModel->returnFromPaypal('token'); $shippingAddress = $quote->getShippingAddress(); + $prefix = ''; - $prefix = 'exported'; $this->assertEquals([$prefix . $this->getExportedData()['street']], $shippingAddress->getStreet()); $this->assertEquals($prefix . $this->getExportedData()['firstname'], $shippingAddress->getFirstname()); $this->assertEquals($prefix . $this->getExportedData()['city'], $shippingAddress->getCity()); $this->assertEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); - // This fields not in exported keys list. Fields the same as quote shipping and billing address. - $this->assertNotEquals($prefix . $this->getExportedData()['region'], $shippingAddress->getRegion()); - $this->assertNotEquals($prefix . $this->getExportedData()['email'], $shippingAddress->getEmail()); + $this->assertEquals($prefix . $this->getExportedData()['email'], $shippingAddress->getEmail()); } /** * The case when handling address data from the checkout. - * System's address fields are not replacing from export Paypal data. + * System's address fields are not replacing from export PayPal data. * * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php * @magentoAppIsolation enabled @@ -326,7 +326,7 @@ public function testReturnFromPaypalButton() */ public function testReturnFromPaypalIfCheckout() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $this->prepareCheckoutModel($quote); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); @@ -342,6 +342,96 @@ public function testReturnFromPaypalIfCheckout() $this->assertNotEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); } + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * After return from PayPal quote billing address have to be updated by PayPal Express address. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoDbIsolation enabled + * + * @return void + */ + public function testReturnFromPayPalForCustomerWithEmptyAddresses(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); + + $this->checkoutModel->returnFromPaypal('token'); + + $billingAddress = $quote->getBillingAddress(); + + $this->performQuoteAddressAssertions($billingAddress, $this->getExportedData()); + } + + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * Default store country is in PayPal Express allowed specific country list. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1 + * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB + * @magentoConfigFixture current_store general/country/default US + * + * @magentoDbIsolation enabled + * + * @return void + */ + public function testPaymentValidationWithAllowedSpecificCountry(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + + $quote->getPayment()->getMethodInstance()->validate(); + } + + /** + * Test case when customer doesn't have either billing or shipping addresses. + * Customer add virtual product to quote and place order using PayPal Express method. + * PayPal Express allowed specific country list doesn't contain default store country. + * + * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoConfigFixture current_store payment/paypal_express/allowspecific 1 + * @magentoConfigFixture current_store payment/paypal_express/specificcountry US,GB + * @magentoConfigFixture current_store general/country/default CA + * + * @magentoDbIsolation enabled + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage You can't use the payment type you selected to make payments to the billing country. + * + * @return void + */ + public function testPaymentValidationWithAllowedSpecificCountryNegative(): void + { + $quote = $this->getFixtureQuote(); + $this->prepareCheckoutModel($quote); + $quote->getPayment()->getMethodInstance()->validate(); + } + + /** + * Performs quote address assertions. + * + * @param Address $address + * @param array $expected + * @return void + */ + private function performQuoteAddressAssertions(Address $address, array $expected): void + { + foreach ($expected as $key => $item) { + $methodName = 'get' . ucfirst($key); + if ($key === 'street') { + $item = [$item]; + } + + $this->assertEquals($item, $address->$methodName(), 'The "'. $key . '" does not match.'); + } + } + /** * Initialize a checkout model mock. * @@ -358,18 +448,15 @@ private function prepareCheckoutModel(Quote $quote) ] ); - $exportedBillingAddress = $this->_getExportedAddressFixture($this->getExportedData()); - $this->api->expects($this->any()) - ->method('getExportedBillingAddress') - ->will($this->returnValue($exportedBillingAddress)); + $exportedBillingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $this->api->method('getExportedBillingAddress') + ->willReturn($exportedBillingAddress); - $exportedShippingAddress = $this->_getExportedAddressFixture($this->getExportedData()); - $this->api->expects($this->any()) - ->method('getExportedShippingAddress') - ->will($this->returnValue($exportedShippingAddress)); + $exportedShippingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $this->api->method('getExportedShippingAddress') + ->willReturn($exportedShippingAddress); - $this->paypalInfo->expects($this->once()) - ->method('importToPayment') + $this->paypalInfo->method('importToPayment') ->with($this->api, $quote->getPayment()); } @@ -380,17 +467,18 @@ private function prepareCheckoutModel(Quote $quote) */ private function getExportedData() { - return - [ - 'company' => 'Testcompany', - 'email' => 'buyeraccountmpi@gmail.com', - 'firstname' => 'testFirstName', - 'country_id' => 'US', - 'region' => 'testRegion', - 'city' => 'testSity', - 'street' => 'testStreet', - 'telephone' => '223344', - ]; + return [ + 'email' => 'customer@example.com', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'country' => 'US', + 'region' => 'Colorado', + 'region_id' => '13', + 'city' => 'Denver', + 'street' => '66 Pearl St', + 'postcode' => '80203', + 'telephone' => '555-555-555', + ]; } /** @@ -402,7 +490,7 @@ private function getExportedData() */ public function testGuestReturnFromPaypal() { - $quote = $this->_getFixtureQuote(); + $quote = $this->getFixtureQuote(); $paypalConfig = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); @@ -439,12 +527,12 @@ public function testGuestReturnFromPaypal() ->method('create') ->will($this->returnValue($api)); - $exportedBillingAddress = $this->_getExportedAddressFixture($quote->getBillingAddress()->getData()); + $exportedBillingAddress = $this->getExportedAddressFixture($quote->getBillingAddress()->getData()); $api->expects($this->any()) ->method('getExportedBillingAddress') ->will($this->returnValue($exportedBillingAddress)); - $exportedShippingAddress = $this->_getExportedAddressFixture($quote->getShippingAddress()->getData()); + $exportedShippingAddress = $this->getExportedAddressFixture($quote->getShippingAddress()->getData()); $api->expects($this->any()) ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); @@ -464,15 +552,27 @@ public function testGuestReturnFromPaypal() * Prepare fixture for exported address. * * @param array $addressData + * @param string $prefix * @return \Magento\Framework\DataObject */ - protected function _getExportedAddressFixture(array $addressData) + private function getExportedAddressFixture(array $addressData, string $prefix = ''): \Magento\Framework\DataObject { - $addressDataKeys = ['firstname', 'lastname', 'street', 'city', 'telephone']; + $addressDataKeys = [ + 'country', + 'firstname', + 'lastname', + 'street', + 'city', + 'telephone', + 'postcode', + 'region', + 'region_id', + 'email', + ]; $result = []; foreach ($addressDataKeys as $key) { if (isset($addressData[$key])) { - $result[$key] = 'exported' . $addressData[$key]; + $result[$key] = $prefix . $addressData[$key]; } } @@ -488,7 +588,7 @@ protected function _getExportedAddressFixture(array $addressData) * * @return Quote */ - protected function _getFixtureQuote() + private function getFixtureQuote(): Quote { /** @var Collection $quoteCollection */ $quoteCollection = $this->objectManager->create(Collection::class); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php new file mode 100644 index 0000000000000..d0c16c2b41f09 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php @@ -0,0 +1,76 @@ +loadArea('adminhtml'); +$objectManager = Bootstrap::getObjectManager(); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); +$customer = $customerRepository->get('customer@example.com'); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_VIRTUAL) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Virtual Product') + ->setSku('virtual-product-express') + ->setPrice(10) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED); + +/** @var StockItemInterface $stockItem */ +$stockItem = $objectManager->create(StockItemInterface::class); +$stockItem->setQty(100) + ->setIsInStock(true); +$extensionAttributes = $product->getExtensionAttributes(); +$extensionAttributes->setStockItem($stockItem); + +/** @var $productRepository ProductRepositoryInterface */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$product = $productRepository->save($product); + +$billingAddress = $objectManager->create(Address::class); +$billingAddress->setAddressType('billing'); + +/** @var $quote Quote */ +$quote = $objectManager->create(Quote::class); +$quote->setCustomerIsGuest(false) + ->setCustomerId($customer->getId()) + ->setCustomer($customer) + ->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()) + ->setReservedOrderId('test02') + ->setBillingAddress($billingAddress); +$item = $objectManager->create(Item::class); +$item->setProduct($product) + ->setPrice($product->getPrice()) + ->setQty(1); +$quote->addItem($item); +$quote->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_EXPRESS); + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->create(CartRepositoryInterface::class); +$quoteRepository->save($quote); +$quote = $quoteRepository->get($quote->getId()); From eeb78a0b9273e6bd896e1cf3737c746deda6d138 Mon Sep 17 00:00:00 2001 From: DianaRusin Date: Wed, 2 May 2018 14:47:18 +0300 Subject: [PATCH 081/236] MAGETWO-90356: Update copyright year --- COPYING.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYING.txt b/COPYING.txt index 2ba7d78d58a25..040bdd5f3ce72 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,4 +1,4 @@ -Copyright © 2013-2018 Magento, Inc. +Copyright © 2013-present Magento, Inc. Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license From cc222c97af804423e83eea26e77cd5ec6370ebc3 Mon Sep 17 00:00:00 2001 From: DianaRusin Date: Wed, 2 May 2018 16:05:28 +0300 Subject: [PATCH 082/236] MAGETWO-90356: Update copyright year --- app/code/Magento/Theme/etc/config.xml | 2 +- app/code/Magento/Theme/i18n/en_US.csv | 2 +- .../Controller/Adminhtml/System/Design/Config/SaveTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index 8b740bc8d09ae..a6984b449d944 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -45,7 +45,7 @@ Disallow: /*SID= Default welcome msg!
- Copyright © 2013-2018 Magento, Inc. All rights reserved. + Copyright © 2013-present Magento, Inc. All rights reserved.
diff --git a/app/code/Magento/Theme/i18n/en_US.csv b/app/code/Magento/Theme/i18n/en_US.csv index 2b8765c36d24a..53b890635fdc1 100644 --- a/app/code/Magento/Theme/i18n/en_US.csv +++ b/app/code/Magento/Theme/i18n/en_US.csv @@ -142,7 +142,7 @@ Empty,Empty "1 column","1 column" Configuration,Configuration "Default welcome msg!","Default welcome msg!" -"Copyright © 2013-2018 Magento, Inc. All rights reserved.","Copyright © 2013-2018 Magento, Inc. All rights reserved." +"Copyright © 2013-present Magento, Inc. All rights reserved.","Copyright © 2013-present Magento, Inc. All rights reserved." "Design Config Grid","Design Config Grid" "Rebuild design config grid index","Rebuild design config grid index" "Admin empty","Admin empty" diff --git a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php index 333ffb01b24ec..1ea2b28986d8a 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php @@ -67,7 +67,7 @@ private function getRequestParams() 'header_logo_height' => '', 'header_logo_alt' => '', 'header_welcome' => 'Default welcome msg!', - 'footer_copyright' => 'Copyright © 2013-2018 Magento, Inc. All rights reserved.', + 'footer_copyright' => 'Copyright © 2013-present Magento, Inc. All rights reserved.', 'footer_absolute_footer' => '', 'default_robots' => 'INDEX,FOLLOW', 'custom_instructions' => '', From e429f0ec77b9330bcda81a41a79a4d4e90921a8a Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Wed, 2 May 2018 13:24:04 -0500 Subject: [PATCH 083/236] MAGETWO-90177: Image broken on storefront with secure key enabled Fix directive parsing and trailing forward slash issues --- .../tests/lib/mage/wysiwygAdapter.test.js | 93 +++++++++++++------ .../wysiwyg/tiny_mce/tinymce4Adapter.js | 8 +- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js index ff510046e5528..4a972d68e6ba5 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js @@ -22,41 +22,74 @@ define([ Constr.prototype = wysiwygAdapter; obj = new Constr(); - obj.initialize('id', { - 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' - }); }); describe('wysiwygAdapter', function () { - var decodedHtml = '

' + - '

', - encodedHtml = '

' + - '' + - '

', - encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace('%2C%2C', '%2C%2C/'); - - describe('"encodeDirectives" method', function () { - it('converts media directive img src to directive URL', function () { - expect(obj.encodeDirectives(decodedHtml)).toEqual(encodedHtml); + describe('encoding and decoding directives', function () { + function runTests(decodedHtml, encodedHtml) { + var encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace('src="([^"]+)', 'src="$1/"'); + + describe('"encodeDirectives" method', function () { + it('converts media directive img src to directive URL', function () { + expect(obj.encodeDirectives(decodedHtml)).toEqual(encodedHtml); + }); + }); + + describe('"decodeDirectives" method', function () { + it( + 'converts directive URL img src without a trailing forward slash ' + + 'to media url without a trailing forward slash', + function () { + expect(obj.decodeDirectives(encodedHtml)).toEqual(decodedHtml); + } + ); + + it('converts directive URL img src with a trailing forward slash ' + + 'to media url without a trailing forward slash', + function () { + expect(obj.decodeDirectives(encodedHtmlWithForwardSlashInImgSrc)).toEqual(decodedHtml); + } + ); + }); + } + + describe('without secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' + }); + }); + + runTests(decodedHtml, encodedHtml); }); - }); - describe('"decodeDirectives" method', function () { - it( - 'converts directive URL img src without a trailing forward slash ' + - 'to media url without a trailing forward slash', - function () { - expect(obj.decodeDirectives(encodedHtml)).toEqual(decodedHtml); - } - ); - - it('converts directive URL img src with a trailing forward slash ' + - 'to media url without a trailing forward slash', - function () { - expect(obj.decodeDirectives(encodedHtmlWithForwardSlashInImgSrc)).toEqual(decodedHtml); - } - ); + describe('with secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + + '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58/'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); + }); }); }); }); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 443ca40862ca4..eba407645c42b 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -575,7 +575,9 @@ define([ * @param {String} directive */ makeDirectiveUrl: function (directive) { - return this.config['directives_url'].replace(/directive.*/, 'directive/___directive/' + directive); + return this.config['directives_url'] + .replace(/directive/, 'directive/___directive/' + directive) + .replace(/\/$/, ''); }, /** @@ -608,9 +610,9 @@ define([ decodeDirectives: function (content) { // escape special chars in directives url to use it in regular expression var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)')); + reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*') + '/?'); - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind + return content.gsub(reg, function (match) { return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); }); }, From 2f9ecc7132b2d17ba16702cdf862146258fa7f5b Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Wed, 2 May 2018 13:45:36 -0500 Subject: [PATCH 084/236] MAGETWO-90177: Image broken on storefront with secure key enabled Apply changes to tinymce3Adapter.js --- .../Magento/Tinymce3/view/base/web/tinymce3Adapter.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js index fd5e9628fecdb..8989cc7e2d6ee 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js +++ b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js @@ -480,7 +480,9 @@ define([ * @param {String} directive */ makeDirectiveUrl: function (directive) { - return this.config['directives_url'].replace(/directive.*/, 'directive/___directive/' + directive); + return this.config['directives_url'] + .replace(/directive/, 'directive/___directive/' + directive) + .replace(/\/$/, ''); }, /** @@ -539,10 +541,10 @@ define([ decodeDirectives: function (content) { // escape special chars in directives url to use it in regular expression var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9%,_-]+)\/?')); + reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*') + '/?'); - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind - return Base64.mageDecode(decodeURIComponent(match[1])).replace(/"/g, '"'); + return content.gsub(reg, function (match) { + return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); }); }, From 4ed0792100f0fb84dc57aac624d4ee26bccf6d81 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 3 May 2018 10:57:46 +0300 Subject: [PATCH 085/236] MAGETWO-90313: Import existing customer with only three columns will override customer group_id and store_id --- .../Model/Import/Customer.php | 19 ++--- .../Model/Import/CustomerTest.php | 76 +++++++++++++++++++ ...r_to_import_with_one_additional_column.csv | 2 + 3 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 016dca2fa526c..b135bc39dd560 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -378,19 +378,11 @@ protected function _prepareDataForUpdate(array $rowData) $this->_newCustomers[$emailInLowercase][$rowData[self::COLUMN_WEBSITE]] = $entityId; } - $entityRow = [ - 'group_id' => empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id'], - 'store_id' => empty($rowData[self::COLUMN_STORE]) ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]], - 'created_at' => $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'updated_at' => $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT), - 'entity_id' => $entityId, - ]; - // password change/set if (isset($rowData['password']) && strlen($rowData['password'])) { $rowData['password_hash'] = $this->_customerModel->hashPassword($rowData['password']); } - + $entityRow = ['entity_id' => $entityId]; // attribute values foreach (array_intersect_key($rowData, $this->_attributes) as $attributeCode => $value) { $attributeParameters = $this->_attributes[$attributeCode]; @@ -429,12 +421,21 @@ protected function _prepareDataForUpdate(array $rowData) if ($newCustomer) { // create + $entityRow['group_id'] = empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id']; + $entityRow['store_id'] = empty($rowData[self::COLUMN_STORE]) + ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + $entityRow['created_at'] = $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COLUMN_WEBSITE]]; $entityRow['email'] = $emailInLowercase; $entityRow['is_active'] = 1; $entitiesToCreate[] = $entityRow; } else { // edit + $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + if (!empty($rowData[self::COLUMN_STORE])) { + $entityRow['store_id'] = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + } $entitiesToUpdate[] = $entityRow; } diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php index 5925eb06500e0..05d9c5d3acb1e 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -135,6 +135,82 @@ public function testImportData() ); } + /** + * Tests importData() method. + * + * @magentoDataFixture Magento/Customer/_files/import_export/customer.php + * + * @return void + */ + public function testImportDataWithOneAdditionalColumn(): void + { + $source = new \Magento\ImportExport\Model\Import\Source\Csv( + __DIR__ . '/_files/customer_to_import_with_one_additional_column.csv', + $this->directoryWrite + ); + + /** @var $customersCollection \Magento\Customer\Model\ResourceModel\Customer\Collection */ + $customersCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\ResourceModel\Customer\Collection::class + ); + $customersCollection->resetData(); + $customersCollection->clear(); + + $this->_model + ->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]) + ->setSource($source) + ->validateData() + ->hasToBeTerminated(); + sleep(1); + $this->_model->importData(); + + $customers = $customersCollection->getItems(); + + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $existingCustomer = $objectManager->get(\Magento\Framework\Registry::class) + ->registry('_fixture/Magento_ImportExport_Customer'); + + $updatedCustomer = $customers[$existingCustomer->getId()]; + + $this->assertNotEquals( + $existingCustomer->getFirstname(), + $updatedCustomer->getFirstname(), + 'Firstname must be changed' + ); + + $this->assertNotEquals( + $existingCustomer->getUpdatedAt(), + $updatedCustomer->getUpdatedAt(), + 'Updated at date must be changed' + ); + + $this->assertEquals( + $existingCustomer->getLastname(), + $updatedCustomer->getLastname(), + 'Lastname must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getStoreId(), + $updatedCustomer->getStoreId(), + 'Store Id must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getCreatedAt(), + $updatedCustomer->getCreatedAt(), + 'Creation date must not be changed' + ); + + $this->assertEquals( + $existingCustomer->getCustomerGroupId(), + $updatedCustomer->getCustomerGroupId(), + 'Customer group must not be changed' + ); + } + /** * Test importData() method (delete behavior) * diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv new file mode 100644 index 0000000000000..fd081e090eb85 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customer_to_import_with_one_additional_column.csv @@ -0,0 +1,2 @@ +email,_website,firstname +CharlesTAlston@teleworm.us,base,Jhon From 7d2c8a5917269f8d32df89e073db696a90af158e Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 3 May 2018 11:55:26 +0300 Subject: [PATCH 086/236] MAGETWO-90144: [Forwardport] Fixed amount discount for whole cart applying an extra cent to the discount amount for --- .../SalesRule/Model/DeltaPriceRound.php | 78 +++++++++++ .../Model/Rule/Action/Discount/CartFixed.php | 59 ++++++++- .../Test/Unit/Model/DeltaPriceRoundTest.php | 102 +++++++++++++++ .../Rule/Action/Discount/CartFixedTest.php | 37 ++++-- .../Rule/Action/Discount/CartFixedTest.php | 123 ++++++++++++++++++ .../_files/coupon_cart_fixed_discount.php | 49 +++++++ 6 files changed, 430 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/SalesRule/Model/DeltaPriceRound.php create mode 100644 app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php diff --git a/app/code/Magento/SalesRule/Model/DeltaPriceRound.php b/app/code/Magento/SalesRule/Model/DeltaPriceRound.php new file mode 100644 index 0000000000000..b080a93ee4c9f --- /dev/null +++ b/app/code/Magento/SalesRule/Model/DeltaPriceRound.php @@ -0,0 +1,78 @@ +priceCurrency = $priceCurrency; + } + + /** + * Round price based on previous rounding operation delta. + * + * @param float $price + * @param string $type + * @return float + */ + public function round(float $price, string $type): float + { + if ($price) { + // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5 + $delta = isset($this->roundingDeltas[$type]) ? $this->roundingDeltas[$type] : 0.000001; + $price += $delta; + $roundPrice = $this->priceCurrency->round($price); + $this->roundingDeltas[$type] = $price - $roundPrice; + $price = $roundPrice; + } + + return $price; + } + + /** + * Reset all deltas. + * + * @return void + */ + public function resetAll(): void + { + $this->roundingDeltas = []; + } + + /** + * Reset deltas by type. + * + * @param string $type + * @return void + */ + public function reset(string $type): void + { + if (isset($this->roundingDeltas[$type])) { + unset($this->roundingDeltas[$type]); + } + } +} diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php index caa938322617d..c53e23321b905 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php +++ b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php @@ -5,6 +5,14 @@ */ namespace Magento\SalesRule\Model\Rule\Action\Discount; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\SalesRule\Model\DeltaPriceRound; +use Magento\SalesRule\Model\Validator; + +/** + * Calculates discount for cart item if fixed discount applied on whole cart. + */ class CartFixed extends AbstractDiscount { /** @@ -14,6 +22,33 @@ class CartFixed extends AbstractDiscount */ protected $_cartFixedRuleUsedForAddress = []; + /** + * @var DeltaPriceRound + */ + private $deltaPriceRound; + + /** + * @var string + */ + private static $discountType = 'CartFixed'; + + /** + * @param Validator $validator + * @param DataFactory $discountDataFactory + * @param PriceCurrencyInterface $priceCurrency + * @param DeltaPriceRound $deltaPriceRound + */ + public function __construct( + Validator $validator, + DataFactory $discountDataFactory, + PriceCurrencyInterface $priceCurrency, + DeltaPriceRound $deltaPriceRound = null + ) { + $this->deltaPriceRound = $deltaPriceRound ?: ObjectManager::getInstance()->get(DeltaPriceRound::class); + + parent::__construct($validator, $discountDataFactory, $priceCurrency); + } + /** * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item @@ -51,14 +86,22 @@ public function calculate($rule, $item, $qty) $cartRules[$rule->getId()] = $rule->getDiscountAmount(); } - if ($cartRules[$rule->getId()] > 0) { + $availableDiscountAmount = (float)$cartRules[$rule->getId()]; + $discountType = self::$discountType . $rule->getId(); + + if ($availableDiscountAmount > 0) { $store = $quote->getStore(); if ($ruleTotals['items_count'] <= 1) { - $quoteAmount = $this->priceCurrency->convert($cartRules[$rule->getId()], $store); - $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]); + $quoteAmount = $this->priceCurrency->convert($availableDiscountAmount, $store); + $baseDiscountAmount = min($baseItemPrice * $qty, $availableDiscountAmount); + $this->deltaPriceRound->reset($discountType); } else { - $discountRate = $baseItemPrice * $qty / $ruleTotals['base_items_price']; - $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate; + $ratio = $baseItemPrice * $qty / $ruleTotals['base_items_price']; + $maximumItemDiscount = $this->deltaPriceRound->round( + $rule->getDiscountAmount() * $ratio, + $discountType + ); + $quoteAmount = $this->priceCurrency->convert($maximumItemDiscount, $store); $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount); @@ -67,7 +110,11 @@ public function calculate($rule, $item, $qty) $baseDiscountAmount = $this->priceCurrency->round($baseDiscountAmount); - $cartRules[$rule->getId()] -= $baseDiscountAmount; + $availableDiscountAmount -= $baseDiscountAmount; + $cartRules[$rule->getId()] = $availableDiscountAmount; + if ($availableDiscountAmount <= 0) { + $this->deltaPriceRound->reset($discountType); + } $discountData->setAmount($this->priceCurrency->round(min($itemPrice * $qty, $quoteAmount))); $discountData->setBaseAmount($baseDiscountAmount); diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php new file mode 100644 index 0000000000000..d67dab5baf63b --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/DeltaPriceRoundTest.php @@ -0,0 +1,102 @@ +priceCurrency = $this->getMockForAbstractClass(PriceCurrencyInterface::class); + $this->priceCurrency->method('round') + ->willReturnCallback( + function ($amount) { + return round($amount, 2); + } + ); + + $this->model = new DeltaPriceRound($this->priceCurrency); + } + + /** + * Tests rounded price based on previous rounding operation delta. + * + * @param array $prices + * @param array $roundedPrices + * @return void + * @dataProvider roundDataProvider + */ + public function testRound(array $prices, array $roundedPrices): void + { + foreach ($prices as $key => $price) { + $roundedPrice = $this->model->round($price, 'test'); + $this->assertEquals($roundedPrices[$key], $roundedPrice); + } + + $this->model->reset('test'); + } + + /** + * @return array + */ + public function roundDataProvider(): array + { + return [ + [ + 'prices' => [1.004, 1.004], + 'rounded prices' => [1.00, 1.01], + ], + [ + 'prices' => [1.005, 1.005], + 'rounded prices' => [1.01, 1.0], + ], + ]; + } + + /** + * @return void + */ + public function testReset(): void + { + $this->assertEquals(1.44, $this->model->round(1.444, 'test')); + $this->model->reset('test'); + $this->assertEquals(1.44, $this->model->round(1.444, 'test')); + } + + /** + * @return void + */ + public function testResetAll(): void + { + $this->assertEquals(1.44, $this->model->round(1.444, 'test1')); + $this->assertEquals(1.44, $this->model->round(1.444, 'test2')); + + $this->model->resetAll(); + + $this->assertEquals(1.44, $this->model->round(1.444, 'test1')); + $this->assertEquals(1.44, $this->model->round(1.444, 'test2')); + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php index 2f1bee9fc686a..13f26124c464d 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/Discount/CartFixedTest.php @@ -5,48 +5,56 @@ */ namespace Magento\SalesRule\Test\Unit\Model\Rule\Action\Discount; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests for Magento\SalesRule\Model\Rule\Action\Discount\CartFixed. + */ class CartFixedTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\Rule|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Rule|MockObject */ protected $rule; /** - * @var \Magento\Quote\Model\Quote\Item\AbstractItem|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Item\AbstractItem|MockObject */ protected $item; /** - * @var \Magento\SalesRule\Model\Validator|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Validator|MockObject */ protected $validator; /** - * @var \Magento\SalesRule\Model\Rule\Action\Discount\Data|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\SalesRule\Model\Rule\Action\Discount\Data|MockObject */ protected $data; /** - * @var \Magento\Quote\Model\Quote|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote|MockObject */ protected $quote; /** - * @var \Magento\Quote\Model\Quote\Address|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Quote\Model\Quote\Address|MockObject */ protected $address; /** - * @var CartFixed + * @var \Magento\SalesRule\Model\Rule\Action\Discount\CartFixed */ protected $model; /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|MockObject */ protected $priceCurrency; + /** + * @inheritdoc + */ protected function setUp() { $this->rule = $this->getMockBuilder(\Magento\Framework\DataObject::class) @@ -66,18 +74,23 @@ protected function setUp() $this->item->expects($this->any())->method('getAddress')->will($this->returnValue($this->address)); $this->validator = $this->createMock(\Magento\SalesRule\Model\Validator::class); + /** @var \Magento\SalesRule\Model\Rule\Action\Discount\DataFactory|MockObject $dataFactory */ $dataFactory = $this->createPartialMock( \Magento\SalesRule\Model\Rule\Action\Discount\DataFactory::class, ['create'] ); $dataFactory->expects($this->any())->method('create')->will($this->returnValue($this->data)); - $this->priceCurrency = $this->getMockBuilder( - \Magento\Framework\Pricing\PriceCurrencyInterface::class - )->getMock(); + $this->priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + ->getMock(); + $deltaPriceRound = $this->getMockBuilder(\Magento\SalesRule\Model\DeltaPriceRound::class) + ->disableOriginalConstructor() + ->getMock(); + $this->model = new \Magento\SalesRule\Model\Rule\Action\Discount\CartFixed( $this->validator, $dataFactory, - $this->priceCurrency + $this->priceCurrency, + $deltaPriceRound ); } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php new file mode 100644 index 0000000000000..0dc245bd7e6e6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php @@ -0,0 +1,123 @@ +cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); + $this->couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); + $this->cartItemRepository = Bootstrap::getObjectManager()->create(GuestCartItemRepositoryInterface::class); + } + + /** + * Applies fixed discount amount on whole cart. + * + * @param array $productPrices + * @return void + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_discount.php + * @dataProvider applyFixedDiscountDataProvider + */ + public function testApplyFixedDiscount(array $productPrices): void + { + $expectedDiscount = '-15.00'; + $couponCode = 'CART_FIXED_DISCOUNT_15'; + $cartId = $this->cartManagement->createEmptyCart(); + + foreach ($productPrices as $price) { + $product = $this->createProduct($price); + + /** @var CartItemInterface $quoteItem */ + $quoteItem = Bootstrap::getObjectManager()->create(CartItemInterface::class); + $quoteItem->setQuoteId($cartId); + $quoteItem->setProduct($product); + $quoteItem->setQty(1); + $this->cartItemRepository->save($quoteItem); + } + + $this->couponManagement->set($cartId, $couponCode); + + /** @var GuestCartTotalRepositoryInterface $cartTotalRepository */ + $cartTotalRepository = Bootstrap::getObjectManager()->get(GuestCartTotalRepositoryInterface::class); + $total = $cartTotalRepository->get($cartId); + + $this->assertEquals($expectedDiscount, $total->getBaseDiscountAmount()); + } + + /** + * @return array + */ + public function applyFixedDiscountDataProvider(): array + { + return [ + 'prices when discount had wrong value 15.01' => [[22, 14, 43, 7.50, 0.00]], + 'prices when discount had wrong value 14.99' => [[47, 33, 9.50, 42, 0.00]], + ]; + } + + /** + * Returns simple product with given price. + * + * @param float $price + * @return ProductInterface + */ + private function createProduct(float $price): ProductInterface + { + $name = 'simple-' . $price; + $productRepository = Bootstrap::getObjectManager()->get(ProductRepository::class); + $product = Bootstrap::getObjectManager()->create(Product::class); + $product->setTypeId('simple') + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName($name) + ->setSku(uniqid($name)) + ->setPrice($price) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['qty' => 1, 'is_in_stock' => 1]) + ->setWeight(1); + + return $productRepository->save($product); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php new file mode 100644 index 0000000000000..08e3ffe6e046c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount.php @@ -0,0 +1,49 @@ +create(Rule::class); +$salesRule->setData( + [ + 'name' => '15$ fixed discount on whole cart', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, + 'conditions' => [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'base_subtotal', + 'operator' => '>', + 'value' => 45, + ], + ], + 'simple_action' => Rule::CART_FIXED_ACTION, + 'discount_amount' => 15, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] +); +$objectManager->get(\Magento\SalesRule\Model\ResourceModel\Rule::class)->save($salesRule); + +// Create coupon and assign "15$ fixed discount" rule to this coupon. +$coupon = $objectManager->create(Coupon::class); +$coupon->setRuleId($salesRule->getId()) + ->setCode('CART_FIXED_DISCOUNT_15') + ->setType(0); +$objectManager->get(CouponRepositoryInterface::class)->save($coupon); From 9e01479c24c8658ed648a12f6bf69a1ccd0a7450 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Thu, 3 May 2018 12:03:09 +0300 Subject: [PATCH 087/236] Refactored code for avoiding copy/paste --- lib/internal/Magento/Framework/File/Csv.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/lib/internal/Magento/Framework/File/Csv.php b/lib/internal/Magento/Framework/File/Csv.php index 10ee9b193d543..571ad6b21efa7 100644 --- a/lib/internal/Magento/Framework/File/Csv.php +++ b/lib/internal/Magento/Framework/File/Csv.php @@ -126,29 +126,23 @@ public function getDataPairs($file, $keyIndex = 0, $valueIndex = 1) /** * Saving data row array into file * - * @param string $file - * @param array $data - * @return $this + * @param string $file + * @param array $data + * @return $this * @throws \Magento\Framework\Exception\FileSystemException * @deprecated * @see appendData */ public function saveData($file, $data) { - $fh = fopen($file, 'w'); - foreach ($data as $dataRow) { - $this->file->filePutCsv($fh, $dataRow, $this->_delimiter, $this->_enclosure); - } - fclose($fh); - return $this; + return $this->appendData($file, $data, 'w'); } /** - * Replace the saveData method by - * allowing to select the input mode + * Replace the saveData method by allowing to select the input mode * - * @param $file - * @param $data + * @param string $file + * @param array $data * @param string $mode * * @return $this From 047668991ad2376b7fe15e64dea74031dabb0cf1 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov Date: Thu, 3 May 2018 12:51:23 +0300 Subject: [PATCH 088/236] magento/magento2#9347: Zend feed refactoring - Fixes based on static tests --- app/code/Magento/Rss/Test/Unit/Model/RssTest.php | 2 +- lib/internal/Magento/Framework/App/Feed.php | 2 ++ lib/internal/Magento/Framework/App/FeedFactory.php | 4 +++- lib/internal/Magento/Framework/App/FeedFactoryInterface.php | 4 +++- lib/internal/Magento/Framework/App/FeedInterface.php | 2 ++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index 08552bab77d81..f2888e4296b40 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -43,7 +43,7 @@ class RssTest extends \PHPUnit\Framework\TestCase http://magento.com/rss/link Sat, 22 Apr 2017 13:21:12 +0200 - Zend_Feed + Zend\Feed http://blogs.law.harvard.edu/tech/rss <![CDATA[Feed 1 Title]]> diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index d69c6e4df64e0..a1fbe185f91b9 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\App; use Zend\Feed\Writer\FeedFactory; diff --git a/lib/internal/Magento/Framework/App/FeedFactory.php b/lib/internal/Magento/Framework/App/FeedFactory.php index 9151d7e836bb7..5f28b447d690a 100644 --- a/lib/internal/Magento/Framework/App/FeedFactory.php +++ b/lib/internal/Magento/Framework/App/FeedFactory.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\App; use Magento\Framework\ObjectManagerInterface; @@ -46,7 +48,7 @@ public function __construct( /** * {@inheritdoc} */ - public function create(array $data, $format = FeedFactoryInterface::FORMAT_RSS) : FeedInterface + public function create(array $data, string $format = FeedFactoryInterface::FORMAT_RSS) : FeedInterface { if (!isset($this->formats[$format])) { throw new \Magento\Framework\Exception\InputException( diff --git a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php index 2abaaa61a4b5c..ac53a366bd127 100644 --- a/lib/internal/Magento/Framework/App/FeedFactoryInterface.php +++ b/lib/internal/Magento/Framework/App/FeedFactoryInterface.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\App; /** @@ -24,5 +26,5 @@ interface FeedFactoryInterface * @param string $format * @return FeedInterface */ - public function create(array $data, $format = self::FORMAT_RSS) : FeedInterface; + public function create(array $data, string $format = self::FORMAT_RSS) : FeedInterface; } diff --git a/lib/internal/Magento/Framework/App/FeedInterface.php b/lib/internal/Magento/Framework/App/FeedInterface.php index 66515c83b895b..7d6346e107cfd 100644 --- a/lib/internal/Magento/Framework/App/FeedInterface.php +++ b/lib/internal/Magento/Framework/App/FeedInterface.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\App; /** From 276082a0fce9d8e3f0673291fe7900ea55dd2a72 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 3 May 2018 14:54:57 +0300 Subject: [PATCH 089/236] MAGETWO-90313: Import existing customer with only three columns will override customer group_id and store_id --- app/code/Magento/CustomerImportExport/Model/Import/Customer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index b135bc39dd560..ad405190262e4 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -423,7 +423,7 @@ protected function _prepareDataForUpdate(array $rowData) // create $entityRow['group_id'] = empty($rowData['group_id']) ? self::DEFAULT_GROUP_ID : $rowData['group_id']; $entityRow['store_id'] = empty($rowData[self::COLUMN_STORE]) - ? 0 : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + ? \Magento\Store\Model\Store::DEFAULT_STORE_ID : $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; $entityRow['created_at'] = $createdAt->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); $entityRow['website_id'] = $this->_websiteCodeToId[$rowData[self::COLUMN_WEBSITE]]; From 79c23fa696c8094d1ea43ac992dc133151dd9deb Mon Sep 17 00:00:00 2001 From: Stas Kozar Date: Thu, 3 May 2018 15:33:24 +0300 Subject: [PATCH 090/236] MAGETWO-90310: [Magento Cloud] - PayPal pop up does not work with virtual product (gift card) --- .../testsuite/Magento/Paypal/Model/Express/CheckoutTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index 982d5857d4221..bd641dab26c09 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -465,7 +465,7 @@ private function prepareCheckoutModel(Quote $quote) * * @return array */ - private function getExportedData() + private function getExportedData(): array { return [ 'email' => 'customer@example.com', From cfefced540cb76da234759e4a135ee2baeaee6b6 Mon Sep 17 00:00:00 2001 From: Stanislav Lopukhov Date: Thu, 3 May 2018 14:52:12 +0300 Subject: [PATCH 091/236] MAGETWO-90563: [Forwardport] Implement segmentation for Category Product Indexer --- .../Model/ResourceModel/Index.php | 25 ++- .../Category/Product/AbstractAction.php | 54 ++++- .../Indexer/Category/Product/Action/Full.php | 84 ++++--- .../Category/Product/Plugin/StoreGroup.php | 32 ++- .../Category/Product/Plugin/StoreView.php | 37 +++ .../Category/Product/Plugin/TableResolver.php | 74 ++++++ .../Category/Product/Plugin/Website.php | 46 ++++ .../Category/Product/TableMaintainer.php | 210 ++++++++++++++++++ .../Catalog/Model/ProductCategoryList.php | 37 ++- .../Catalog/Model/ResourceModel/Product.php | 55 +++-- .../ResourceModel/Product/Collection.php | 22 +- .../Catalog/Model/ResourceModel/Url.php | 88 +++++--- .../Setup/Patch/Data/EnableSegmentation.php | 90 ++++++++ .../Setup/Patch/Schema/EnableSegmentation.php | 85 +++++++ .../Category/Product/Plugin/StoreViewTest.php | 26 ++- app/code/Magento/Catalog/etc/adminhtml/di.xml | 3 + app/code/Magento/Catalog/etc/frontend/di.xml | 3 + .../Magento/Catalog/etc/webapi_rest/di.xml | 4 +- .../Magento/Catalog/etc/webapi_soap/di.xml | 3 + .../Aggregation/Category/DataProvider.php | 27 ++- .../Search/FilterMapper/ExclusionStrategy.php | 27 ++- .../Unit/Model/ResourceModel/IndexTest.php | 34 ++- 22 files changed, 942 insertions(+), 124 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php create mode 100644 app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php create mode 100644 app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php create mode 100644 app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php create mode 100644 app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php index 8721b600ed396..c2379e9dff062 100644 --- a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php @@ -10,6 +10,10 @@ use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Search\Request\Dimension; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; /** * @api @@ -29,22 +33,30 @@ class Index extends AbstractDb */ protected $metadataPool; + /** + * @var TableResolver + */ + private $tableResolver; + /** * Index constructor. * @param Context $context * @param StoreManagerInterface $storeManager * @param MetadataPool $metadataPool * @param null $connectionName + * @param TableResolver|null $tableResolver */ public function __construct( Context $context, StoreManagerInterface $storeManager, MetadataPool $metadataPool, - $connectionName = null + $connectionName = null, + TableResolver $tableResolver = null ) { parent::__construct($context, $connectionName); $this->storeManager = $storeManager; $this->metadataPool = $metadataPool; + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -116,8 +128,17 @@ public function getCategoryProductIndexData($storeId = null, $productIds = null) { $connection = $this->getConnection(); + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $storeId); + + $catalogCategoryProductTableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + $select = $connection->select()->from( - [$this->getTable('catalog_category_product_index')], + [$catalogCategoryProductTableName], ['category_id', 'product_id', 'position', 'store_id'] )->where( 'store_id = ?', diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php index 3270416137b67..40f46453a5024 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php @@ -4,16 +4,19 @@ * See COPYING.txt for license details. */ +// @codingStandardsIgnoreFile + namespace Magento\Catalog\Model\Indexer\Category\Product; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Framework\App\ObjectManager; -use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Class AbstractAction @@ -42,6 +45,7 @@ abstract class AbstractAction /** * Suffix for table to show it is temporary + * @deprecated */ const TEMPORARY_TABLE_SUFFIX = '_tmp'; @@ -106,6 +110,11 @@ abstract class AbstractAction */ protected $metadataPool; + /** + * @var TableMaintainer + */ + protected $tableMaintainer; + /** * @var string * @since 101.0.0 @@ -121,15 +130,17 @@ abstract class AbstractAction * @param ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Model\Config $config - * @param QueryGenerator|null $queryGenerator + * @param QueryGenerator $queryGenerator * @param MetadataPool|null $metadataPool + * @param TableMaintainer|null $tableMaintainer */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Config $config, QueryGenerator $queryGenerator = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + TableMaintainer $tableMaintainer = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -137,6 +148,7 @@ public function __construct( $this->config = $config; $this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class); $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -180,6 +192,7 @@ protected function getTable($table) * The name is switched between 'catalog_category_product_index' and 'catalog_category_product_index_replica' * * @return string + * @deprecated */ protected function getMainTable() { @@ -190,6 +203,7 @@ protected function getMainTable() * Return temporary index table name * * @return string + * @deprecated */ protected function getMainTmpTable() { @@ -198,6 +212,19 @@ protected function getMainTmpTable() : $this->getMainTable(); } + /** + * Return index table name + * + * @param int $storeId + * @return string + */ + protected function getIndexTable($storeId) + { + return $this->useTempTable + ? $this->tableMaintainer->getMainReplicaTable($storeId) + : $this->tableMaintainer->getMainTable($storeId); + } + /** * Return category path by id * @@ -319,7 +346,7 @@ protected function getNonAnchorCategoriesSelect(Store $store) } /** - * Add filtering by child products to select. + * Add filtering by child products to select * * It's used for correct handling of composite products. * This method makes assumption that select already joins `catalog_product_entity` as `cpe`. @@ -387,11 +414,8 @@ protected function isRangingNeeded() * @param int $range * @return Select[] */ - protected function prepareSelectsByRange( - Select $select, - string $field, - int $range = self::RANGE_CATEGORY_STEP - ) { + protected function prepareSelectsByRange(Select $select, $field, $range = self::RANGE_CATEGORY_STEP) + { if ($this->isRangingNeeded()) { $iterator = $this->queryGenerator->generate( $field, @@ -422,7 +446,7 @@ protected function reindexNonAnchorCategories(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) @@ -612,6 +636,12 @@ protected function makeTempCategoryTreeIndex() ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY] ); + $temporaryTable->addIndex( + 'child_id', + ['child_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_INDEX] + ); + // Drop the temporary table in case it already exists on this (persistent?) connection. $this->connection->dropTemporaryTable($temporaryName); $this->connection->createTemporaryTable($temporaryTable); @@ -678,7 +708,7 @@ protected function reindexAnchorCategories(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) @@ -804,7 +834,7 @@ protected function reindexRootCategory(Store $store) $this->connection->query( $this->connection->insertFromSelect( $select, - $this->getMainTmpTable(), + $this->getIndexTable($store->getId()), ['category_id', 'product_id', 'position', 'is_parent', 'store_id', 'visibility'], \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php index cde186a7e44af..09dbed350c5e4 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -88,18 +88,35 @@ public function __construct( } /** - * - * Clear the table we'll be writing de-normalized data into - * to prevent archived data getting in the way of actual data. - * * @return void */ - private function clearCurrentTable() + private function createTables() { - $this->connection->delete( - $this->activeTableSwitcher - ->getAdditionalTableName($this->getMainTable()) - ); + foreach ($this->storeManager->getStores() as $store) { + $this->tableMaintainer->createTablesForStore($store->getId()); + } + } + + /** + * @return void + */ + private function clearReplicaTables() + { + foreach ($this->storeManager->getStores() as $store) { + $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable($store->getId())); + } + } + + /** + * @return void + */ + private function switchTables() + { + $tablesToSwitch = []; + foreach ($this->storeManager->getStores() as $store) { + $tablesToSwitch[] = $this->tableMaintainer->getMainTable($store->getId()); + } + $this->activeTableSwitcher->switchTable($this->connection, $tablesToSwitch); } /** @@ -109,22 +126,26 @@ private function clearCurrentTable() */ public function execute() { - $this->clearCurrentTable(); + $this->createTables(); + $this->clearReplicaTables(); $this->reindex(); - $this->activeTableSwitcher->switchTable($this->connection, [$this->getMainTable()]); + $this->switchTables(); return $this; } /** - * Publish data from tmp to index + * Publish data from tmp to replica table * + * @param \Magento\Store\Model\Store $store * @return void */ - protected function publishData() + private function publishData($store) { - $select = $this->connection->select()->from($this->getMainTmpTable()); - $columns = array_keys($this->connection->describeTable($this->getMainTable())); - $tableName = $this->activeTableSwitcher->getAdditionalTableName($this->getMainTable()); + $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable($store->getId())); + $columns = array_keys( + $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable($store->getId())) + ); + $tableName = $this->tableMaintainer->getMainReplicaTable($store->getId()); $this->connection->query( $this->connection->insertFromSelect( @@ -136,23 +157,13 @@ protected function publishData() ); } - /** - * Clear all index data - * - * @return void - */ - protected function clearTmpData() - { - $this->connection->delete($this->getMainTmpTable()); - } - /** * {@inheritdoc} */ protected function reindexRootCategory(\Magento\Store\Model\Store $store) { if ($this->isIndexRootCategoryNeeded()) { - $this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)'); + $this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)', $store); } } @@ -164,7 +175,7 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store) */ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) { - $this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)'); + $this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } /** @@ -175,7 +186,7 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store) */ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) { - $this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)'); + $this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store); } /** @@ -183,12 +194,17 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store) * * @param \Magento\Framework\DB\Select $basicSelect * @param string $whereCondition + * @param \Magento\Store\Model\Store $store * @return void */ - private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition) + private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition, $store) { + $this->tableMaintainer->createMainTmpTable($store->getId()); + $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - $columns = array_keys($this->connection->describeTable($this->getMainTmpTable())); + $columns = array_keys( + $this->connection->describeTable($this->tableMaintainer->getMainTmpTable($store->getId())) + ); $this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount); $batches = $this->batchProvider->getBatches( $this->connection, @@ -197,7 +213,7 @@ private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSe $this->batchRowsCount ); foreach ($batches as $batch) { - $this->clearTmpData(); + $this->connection->delete($this->tableMaintainer->getMainTmpTable($store->getId())); $resultSelect = clone $basicSelect; $select = $this->connection->select(); $select->distinct(true); @@ -207,12 +223,12 @@ private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSe $this->connection->query( $this->connection->insertFromSelect( $resultSelect, - $this->getMainTmpTable(), + $this->tableMaintainer->getMainTmpTable($store->getId()), $columns, \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ) ); - $this->publishData(); + $this->publishData($store); } } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php index 2ee46b3a6096b..9f4e19bf95a8d 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php @@ -9,6 +9,7 @@ use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\Framework\Model\AbstractModel; use Magento\Catalog\Model\Indexer\Category\Product; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; class StoreGroup { @@ -22,12 +23,21 @@ class StoreGroup */ protected $indexerRegistry; + /** + * @var TableMaintainer + */ + protected $tableMaintainer; + /** * @param IndexerRegistry $indexerRegistry + * @param TableMaintainer $tableMaintainer */ - public function __construct(IndexerRegistry $indexerRegistry) - { + public function __construct( + IndexerRegistry $indexerRegistry, + TableMaintainer $tableMaintainer + ) { $this->indexerRegistry = $indexerRegistry; + $this->tableMaintainer = $tableMaintainer; } /** @@ -73,4 +83,22 @@ protected function validate(AbstractModel $group) return ($group->dataHasChangedFor('website_id') || $group->dataHasChangedFor('root_category_id')) && !$group->isObjectNew(); } + + /** + * Delete catalog_category_product indexer tables for deleted store group + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $storeGroup + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $storeGroup) + { + foreach ($storeGroup->getStores() as $store) { + $this->tableMaintainer->dropTablesForStore($store->getId()); + } + return $objectResource; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php index f49b685ba6f7f..114d2a94f5b35 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php @@ -5,6 +5,9 @@ */ namespace Magento\Catalog\Model\Indexer\Category\Product\Plugin; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\AbstractModel; + class StoreView extends StoreGroup { /** @@ -17,4 +20,38 @@ protected function validate(\Magento\Framework\Model\AbstractModel $store) { return $store->isObjectNew() || $store->dataHasChangedFor('group_id'); } + + /** + * Invalidate catalog_category_product indexer + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $store + * + * @return AbstractDb + */ + public function afterSave(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store = null) + { + if ($store->isObjectNew()) { + $this->tableMaintainer->createTablesForStore($store->getId()); + } + + return parent::afterSave($subject, $objectResource); + } + + /** + * Delete catalog_category_product indexer table for deleted store + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $store + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store) + { + $this->tableMaintainer->dropTablesForStore($store->getId()); + return $objectResource; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php new file mode 100644 index 0000000000000..936e6163cbcc5 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/TableResolver.php @@ -0,0 +1,74 @@ +storeManager = $storeManager; + $this->tableResolver = $tableResolver; + } + + /** + * Replacing catalog_category_product_index table name on the table name segmented per store + * + * @param ResourceConnection $subject + * @param string $result + * @param string|string[] $modelEntity + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return string + */ + public function afterGetTableName( + \Magento\Framework\App\ResourceConnection $subject, + string $result, + $modelEntity + ) { + if (!is_array($modelEntity) && $modelEntity === AbstractAction::MAIN_INDEX_TABLE) { + $catalogCategoryProductDimension = new Dimension( + \Magento\Store\Model\Store::ENTITY, + $this->storeManager->getStore()->getId() + ); + + $tableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + return $tableName; + } + return $result; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php new file mode 100644 index 0000000000000..387a8085310e4 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php @@ -0,0 +1,46 @@ +tableMaintainer = $tableMaintainer; + } + + /** + * Delete catalog_category_product indexer tables for deleted website + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $website + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website) + { + foreach ($website->getStoreIds() as $storeId) { + $this->tableMaintainer->dropTablesForStore($storeId); + } + return $objectResource; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php new file mode 100644 index 0000000000000..d2f8925d09a7b --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php @@ -0,0 +1,210 @@ +resource = $resource; + $this->tableResolver = $tableResolver; + } + + /** + * Get connection + * + * @return AdapterInterface + */ + private function getConnection() + { + if (!isset($this->connection)) { + $this->connection = $this->resource->getConnection(); + } + return $this->connection; + } + + /** + * Return validated table name + * + * @param string|string[] $table + * @return string + */ + private function getTable($table) + { + return $this->resource->getTableName($table); + } + + /** + * Create table based on main table + * + * @param string $mainTableName + * @param string $newTableName + * + * @return void + */ + private function createTable($mainTableName, $newTableName) + { + if (!$this->getConnection()->isTableExists($newTableName)) { + $this->getConnection()->createTable( + $this->getConnection()->createTableByDdl($mainTableName, $newTableName) + ); + } + } + + /** + * Drop table + * + * @param string $tableName + * + * @return void + */ + private function dropTable($tableName) + { + if ($this->getConnection()->isTableExists($tableName)) { + $this->getConnection()->dropTable($tableName); + } + } + + /** + * Return main index table name + * + * @param $storeId + * + * @return string + */ + public function getMainTable(int $storeId) + { + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $storeId); + + return $this->tableResolver->resolve(AbstractAction::MAIN_INDEX_TABLE, [$catalogCategoryProductDimension]); + } + + /** + * Create main and replica index tables for store + * + * @param $storeId + * + * @return void + */ + public function createTablesForStore(int $storeId) + { + $mainTableName = $this->getMainTable($storeId); + //Create index table for store based on on main replica table + //Using main replica table is necessary for backward capability and TableResolver plugin work + $this->createTable( + $this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainTableName + ); + + $mainReplicaTableName = $this->getMainTable($storeId) . $this->additionalTableSuffix; + //Create replica table for store based on main replica table + $this->createTable( + $this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainReplicaTableName + ); + } + + /** + * Drop main and replica index tables for store + * + * @param $storeId + * + * @return void + */ + public function dropTablesForStore(int $storeId) + { + $mainTableName = $this->getMainTable($storeId); + $this->dropTable($mainTableName); + + $mainReplicaTableName = $this->getMainTable($storeId) . $this->additionalTableSuffix; + $this->dropTable($mainReplicaTableName); + } + + /** + * Return replica index table name + * + * @param $storeId + * + * @return string + */ + public function getMainReplicaTable(int $storeId) + { + return $this->getMainTable($storeId) . $this->additionalTableSuffix; + } + + /** + * Create temporary index table for store + * + * @param $storeId + * + * @return void + */ + public function createMainTmpTable(int $storeId) + { + if (!isset($this->mainTmpTable[$storeId])) { + $originTableName = $this->getMainTable($storeId); + $temporaryTableName = $this->getMainTable($storeId) . $this->tmpTableSuffix; + $this->getConnection()->createTemporaryTableLike($temporaryTableName, $originTableName, true); + $this->mainTmpTable[$storeId] = $temporaryTableName; + } + } + + /** + * Return temporary index table name + * + * @param $storeId + * + * @return string + */ + public function getMainTmpTable(int $storeId) + { + return $this->mainTmpTable[$storeId]; + } +} diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php index ae875453be938..5bbae772d5c2b 100644 --- a/app/code/Magento/Catalog/Model/ProductCategoryList.php +++ b/app/code/Magento/Catalog/Model/ProductCategoryList.php @@ -5,9 +5,11 @@ */ namespace Magento\Catalog\Model; -use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; use Magento\Framework\DB\Select; use Magento\Framework\DB\Sql\UnionExpression; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Provides info about product categories. @@ -29,16 +31,32 @@ class ProductCategoryList */ private $category; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * @param ResourceModel\Product $productResource * @param ResourceModel\Category $category + * @param StoreManagerInterface $storeManager + * @param TableMaintainer|null $tableMaintainer */ public function __construct( ResourceModel\Product $productResource, - ResourceModel\Category $category + ResourceModel\Category $category, + StoreManagerInterface $storeManager = null, + TableMaintainer $tableMaintainer = null ) { $this->productResource = $productResource; $this->category = $category; + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -50,14 +68,15 @@ public function __construct( public function getCategoryIds($productId) { if (!isset($this->categoryIdList[$productId])) { + $unionTables[] = $this->getCategorySelect($productId, $this->category->getCategoryProductTable()); + foreach ($this->storeManager->getStores() as $store) { + $unionTables[] = $this->getCategorySelect( + $productId, + $this->tableMaintainer->getMainTable($store->getId()) + ); + } $unionSelect = new UnionExpression( - [ - $this->getCategorySelect($productId, $this->category->getCategoryProductTable()), - $this->getCategorySelect( - $productId, - $this->productResource->getTable(AbstractAction::MAIN_INDEX_TABLE) - ) - ], + $unionTables, Select::SQL_UNION_ALL ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php index 81e9473e053b6..d71ec23881982 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php @@ -7,6 +7,7 @@ use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Product entity resource model @@ -83,6 +84,11 @@ class Product extends AbstractResource */ private $productCategoryLink; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * @param \Magento\Eav\Model\Entity\Context $context * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -94,6 +100,7 @@ class Product extends AbstractResource * @param \Magento\Eav\Model\Entity\TypeFactory $typeFactory * @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes * @param array $data + * @param TableMaintainer|null $tableMaintainer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -107,7 +114,8 @@ public function __construct( \Magento\Eav\Model\Entity\Attribute\SetFactory $setFactory, \Magento\Eav\Model\Entity\TypeFactory $typeFactory, \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes, - $data = [] + $data = [], + TableMaintainer $tableMaintainer = null ) { $this->_categoryCollectionFactory = $categoryCollectionFactory; $this->_catalogCategory = $catalogCategory; @@ -122,6 +130,7 @@ public function __construct( $data ); $this->connectionName = 'catalog'; + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -366,22 +375,42 @@ public function getAvailableInCategories($object) // fetching all parent IDs, including those are higher on the tree $entityId = (int)$object->getEntityId(); if (!isset($this->availableCategoryIdsCache[$entityId])) { - $this->availableCategoryIdsCache[$entityId] = $this->getConnection()->fetchCol( - $this->getConnection()->select()->distinct()->from( - $this->getTable('catalog_category_product_index'), - ['category_id'] - )->where( - 'product_id = ? AND is_parent = 1', - $entityId - )->where( - 'visibility != ?', - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE - ) + foreach ($this->_storeManager->getStores() as $store) { + $unionTables[] = $this->getAvailableInCategoriesSelect( + $entityId, + $this->tableMaintainer->getMainTable($store->getId()) + ); + } + $unionSelect = new \Magento\Framework\DB\Sql\UnionExpression( + $unionTables, + \Magento\Framework\DB\Select::SQL_UNION_ALL ); + $this->availableCategoryIdsCache[$entityId] = array_unique($this->getConnection()->fetchCol($unionSelect)); } return $this->availableCategoryIdsCache[$entityId]; } + /** + * Returns DB select for available categories. + * + * @param int $entityId + * @param string $tableName + * @return \Magento\Framework\DB\Select + */ + private function getAvailableInCategoriesSelect($entityId, $tableName) + { + return $this->getConnection()->select()->distinct()->from( + $tableName, + ['category_id'] + )->where( + 'product_id = ? AND is_parent = 1', + $entityId + )->where( + 'visibility != ?', + \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE + ); + } + /** * Get default attribute source model * @@ -402,7 +431,7 @@ public function getDefaultAttributeSourceModel() public function canBeShowInCategory($product, $categoryId) { $select = $this->getConnection()->select()->from( - $this->getTable('catalog_category_product_index'), + $this->tableMaintainer->getMainTable($product->getStoreId()), 'product_id' )->where( 'product_id = ?', diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 0729f2bd1f8f5..a4530b6c47087 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +// @codingStandardsIgnoreFile + namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; @@ -16,6 +18,7 @@ use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Product collection @@ -270,6 +273,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $backend; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * Collection constructor * @@ -295,6 +303,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool + * @param TableMaintainer|null $tableMaintainer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -320,7 +329,8 @@ public function __construct( GroupManagementInterface $groupManagement, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, ProductLimitationFactory $productLimitationFactory = null, - MetadataPool $metadataPool = null + MetadataPool $metadataPool = null, + TableMaintainer $tableMaintainer = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -350,6 +360,7 @@ public function __construct( $storeManager, $connection ); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -467,10 +478,7 @@ public function isEnabledFlat() protected function _construct() { if ($this->isEnabledFlat()) { - $this->_init( - \Magento\Catalog\Model\Product::class, - \Magento\Catalog\Model\ResourceModel\Product\Flat::class - ); + $this->_init(\Magento\Catalog\Model\Product::class, \Magento\Catalog\Model\ResourceModel\Product\Flat::class); } else { $this->_init(\Magento\Catalog\Model\Product::class, \Magento\Catalog\Model\ResourceModel\Product::class); } @@ -1193,7 +1201,7 @@ public function getProductCountSelect() )->distinct( false )->join( - ['count_table' => $this->getTable('catalog_category_product_index')], + ['count_table' => $this->tableMaintainer->getMainTable($this->getStoreId())], 'count_table.product_id = e.entity_id', [ 'count_table.category_id', @@ -1967,7 +1975,7 @@ protected function _applyProductLimitations() $this->getSelect()->setPart(\Magento\Framework\DB\Select::FROM, $fromPart); } else { $this->getSelect()->join( - ['cat_index' => $this->getTable('catalog_category_product_index')], + ['cat_index' => $this->tableMaintainer->getMainTable($this->getStoreId())], $joinCond, ['cat_index_position' => 'position'] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Url.php b/app/code/Magento/Catalog/Model/ResourceModel/Url.php index 682f5c1f45e31..be95f088a2477 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Url.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Url.php @@ -14,6 +14,7 @@ use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Class Url @@ -101,6 +102,11 @@ class Url extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $metadataPool; + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * Url constructor. * @param \Magento\Framework\Model\ResourceModel\Db\Context $context @@ -110,6 +116,7 @@ class Url extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb * @param \Magento\Catalog\Model\Category $catalogCategory * @param \Psr\Log\LoggerInterface $logger * @param null $connectionName + * @param TableMaintainer|null $tableMaintainer */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -118,7 +125,8 @@ public function __construct( Product $productResource, \Magento\Catalog\Model\Category $catalogCategory, \Psr\Log\LoggerInterface $logger, - $connectionName = null + $connectionName = null, + TableMaintainer $tableMaintainer = null ) { $this->_storeManager = $storeManager; $this->_eavConfig = $eavConfig; @@ -126,6 +134,7 @@ public function __construct( $this->_catalogCategory = $catalogCategory; $this->_logger = $logger; parent::__construct($context, $connectionName); + $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); } /** @@ -655,43 +664,52 @@ public function getRewriteByProductStore(array $products) } $connection = $this->getConnection(); - $select = $connection->select()->from( - ['i' => $this->getTable('catalog_category_product_index')], - ['product_id', 'store_id', 'visibility'] - )->joinLeft( - ['u' => $this->getMainTable()], - 'i.product_id = u.entity_id AND i.store_id = u.store_id' - . ' AND u.entity_type = "' . ProductUrlRewriteGenerator::ENTITY_TYPE . '"', - ['request_path'] - )->joinLeft( - ['r' => $this->getTable('catalog_url_rewrite_product_category')], - 'u.url_rewrite_id = r.url_rewrite_id AND r.category_id is NULL', - [] - ); - - $bind = []; + $storesProducts = []; foreach ($products as $productId => $storeId) { - $catId = $this->_storeManager->getStore($storeId)->getRootCategoryId(); - $productBind = 'product_id' . $productId; - $storeBind = 'store_id' . $storeId; - $catBind = 'category_id' . $catId; - $cond = '(' . implode( - ' AND ', - ['i.product_id = :' . $productBind, 'i.store_id = :' . $storeBind, 'i.category_id = :' . $catBind] - ) . ')'; - $bind[$productBind] = $productId; - $bind[$storeBind] = $storeId; - $bind[$catBind] = $catId; - $select->orWhere($cond); + $storesProducts[$storeId][] = $productId; } - $rowSet = $connection->fetchAll($select, $bind); - foreach ($rowSet as $row) { - $result[$row['product_id']] = [ - 'store_id' => $row['store_id'], - 'visibility' => $row['visibility'], - 'url_rewrite' => $row['request_path'], - ]; + foreach ($storesProducts as $storeId => $productIds) { + $select = $connection->select()->from( + ['i' => $this->tableMaintainer->getMainTable($storeId)], + ['product_id', 'store_id', 'visibility'] + )->joinLeft( + ['u' => $this->getMainTable()], + 'i.product_id = u.entity_id AND i.store_id = u.store_id' + . ' AND u.entity_type = "' . ProductUrlRewriteGenerator::ENTITY_TYPE . '"', + ['request_path'] + )->joinLeft( + ['r' => $this->getTable('catalog_url_rewrite_product_category')], + 'u.url_rewrite_id = r.url_rewrite_id AND r.category_id is NULL', + [] + ); + + $bind = []; + foreach ($productIds as $productId) { + $catId = $this->_storeManager->getStore($storeId)->getRootCategoryId(); + $productBind = 'product_id' . $productId; + $storeBind = 'store_id' . $storeId; + $catBind = 'category_id' . $catId; + $bindArray = [ + 'i.product_id = :' . $productBind, + 'i.store_id = :' . $storeBind, + 'i.category_id = :' . $catBind + ]; + $cond = '(' . implode(' AND ', $bindArray) . ')'; + $bind[$productBind] = $productId; + $bind[$storeBind] = $storeId; + $bind[$catBind] = $catId; + $select->orWhere($cond); + } + + $rowSet = $connection->fetchAll($select, $bind); + foreach ($rowSet as $row) { + $result[$row['product_id']] = [ + 'store_id' => $row['store_id'], + 'visibility' => $row['visibility'], + 'url_rewrite' => $row['request_path'], + ]; + } } return $result; diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php b/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php new file mode 100644 index 0000000000000..d7b683a439ff1 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Data/EnableSegmentation.php @@ -0,0 +1,90 @@ +moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->startSetup(); + $setup = $this->moduleDataSetup; + + $catalogCategoryProductIndexColumns = array_keys( + $setup->getConnection()->describeTable($setup->getTable('catalog_category_product_index')) + ); + $storeSelect = $setup->getConnection()->select()->from($setup->getTable('store'))->where('store_id > 0'); + foreach ($setup->getConnection()->fetchAll($storeSelect) as $store) { + $catalogCategoryProductIndexSelect = $setup->getConnection()->select() + ->from( + $setup->getTable('catalog_category_product_index') + )->where( + 'store_id = ?', + $store['store_id'] + ); + $indexTable = $setup->getTable('catalog_category_product_index') . + '_' . + \Magento\Store\Model\Store::ENTITY . + $store['store_id']; + $setup->getConnection()->query( + $setup->getConnection()->insertFromSelect( + $catalogCategoryProductIndexSelect, + $indexTable, + $catalogCategoryProductIndexColumns, + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ) + ); + } + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index')); + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index_replica')); + $setup->getConnection()->delete($setup->getTable('catalog_category_product_index_tmp')); + + $this->moduleDataSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php b/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php new file mode 100644 index 0000000000000..8ae84f9f8e321 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Schema/EnableSegmentation.php @@ -0,0 +1,85 @@ +schemaSetup = $schemaSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->schemaSetup->startSetup(); + $setup = $this->schemaSetup; + + $storeSelect = $setup->getConnection()->select()->from($setup->getTable('store'))->where('store_id > 0'); + foreach ($setup->getConnection()->fetchAll($storeSelect) as $store) { + $indexTable = $setup->getTable('catalog_category_product_index') . + '_' . + \Magento\Store\Model\Store::ENTITY . + $store['store_id']; + if (!$setup->getConnection()->isTableExists($indexTable)) { + $setup->getConnection()->createTable( + $setup->getConnection()->createTableByDdl( + $setup->getTable('catalog_category_product_index'), + $indexTable + ) + ); + } + if (!$setup->getConnection()->isTableExists($indexTable . '_replica')) { + $setup->getConnection()->createTable( + $setup->getConnection()->createTableByDdl( + $setup->getTable('catalog_category_product_index'), + $indexTable . '_replica' + ) + ); + } + } + + $this->schemaSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php index 6e3cd6ed30b52..4da831f5257d0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php @@ -33,6 +33,11 @@ class StoreViewTest extends \PHPUnit\Framework\TestCase */ protected $indexerRegistryMock; + /** + * @var \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer|\PHPUnit_Framework_MockObject_MockObject + */ + protected $tableMaintainer; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -51,15 +56,30 @@ protected function setUp() ); $this->subject = $this->createMock(Group::class); $this->indexerRegistryMock = $this->createPartialMock(IndexerRegistry::class, ['get']); - $this->storeMock = $this->createPartialMock(Store::class, ['isObjectNew', 'dataHasChangedFor', '__wakeup']); + $this->storeMock = $this->createPartialMock( + Store::class, + [ + 'isObjectNew', + 'getId', + 'dataHasChangedFor', + '__wakeup' + ] + ); + $this->tableMaintainer = $this->createPartialMock( + \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer::class, + [ + 'createTablesForStore' + ] + ); - $this->model = new StoreView($this->indexerRegistryMock); + $this->model = new StoreView($this->indexerRegistryMock, $this->tableMaintainer); } public function testAroundSaveNewObject() { $this->mockIndexerMethods(); - $this->storeMock->expects($this->once())->method('isObjectNew')->willReturn(true); + $this->storeMock->expects($this->atLeastOnce())->method('isObjectNew')->willReturn(true); + $this->storeMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); $this->model->beforeSave($this->subject, $this->storeMock); $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->storeMock)); } diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 10251d35dffcd..ca8390a6c8f8a 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -220,4 +220,7 @@ Magento\Catalog\Ui\DataProvider\Product\AddSearchKeyConditionToCollection + + + diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 3989a62a56cc4..659ba2b731366 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -100,4 +100,7 @@ + + + diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml index 1d2b013f2035d..a0d3e850b3c64 100644 --- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml @@ -8,7 +8,6 @@ - @@ -16,4 +15,7 @@ + + + diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml index 98a8ef4de8408..a0d3e850b3c64 100644 --- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml @@ -15,4 +15,7 @@ + + + diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php index 6bf735e2141cc..182ecf873d77a 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php @@ -12,7 +12,13 @@ use Magento\Framework\DB\Select; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\Dimension; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class DataProvider { /** @@ -32,20 +38,28 @@ class DataProvider */ protected $categoryFactory; + /** + * @var TableResolver + */ + private $tableResolver; + /** * DataProvider constructor. * @param ResourceConnection $resource * @param ScopeResolverInterface $scopeResolver * @param Resolver $layerResolver + * @param TableResolver|null $tableResolver */ public function __construct( ResourceConnection $resource, ScopeResolverInterface $scopeResolver, - Resolver $layerResolver + Resolver $layerResolver, + TableResolver $tableResolver = null ) { $this->resource = $resource; $this->scopeResolver = $scopeResolver; $this->layer = $layerResolver->get(); + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -69,9 +83,18 @@ public function aroundGetDataSet( $currentScopeId = $this->scopeResolver->getScope($dimensions['scope']->getValue())->getId(); $currentCategory = $this->layer->getCurrentCategory(); + $catalogCategoryProductDimension = new Dimension(\Magento\Store\Model\Store::ENTITY, $currentScopeId); + + $catalogCategoryProductTableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); + $derivedTable = $this->resource->getConnection()->select(); $derivedTable->from( - ['main_table' => $this->resource->getTableName('catalog_category_product_index')], + ['main_table' => $catalogCategoryProductTableName], [ 'value' => 'category_id' ] diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php index dadce2ed0240c..66e0457e7fadd 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php @@ -7,6 +7,10 @@ namespace Magento\CatalogSearch\Model\Search\FilterMapper; use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Search\Request\Dimension; +use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; /** * Strategy which processes exclusions from general rules @@ -34,19 +38,27 @@ class ExclusionStrategy implements FilterStrategyInterface */ private $validFields = ['price', 'category_ids']; + /** + * @var TableResolver + */ + private $tableResolver; + /** * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param AliasResolver $aliasResolver + * @param TableResolver|null $tableResolver */ public function __construct( \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Store\Model\StoreManagerInterface $storeManager, - AliasResolver $aliasResolver + AliasResolver $aliasResolver, + TableResolver $tableResolver = null ) { $this->resourceConnection = $resourceConnection; $this->storeManager = $storeManager; $this->aliasResolver = $aliasResolver; + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); } /** @@ -112,7 +124,18 @@ private function applyCategoryFilter( \Magento\Framework\DB\Select $select ) { $alias = $this->aliasResolver->getAlias($filter); - $tableName = $this->resourceConnection->getTableName('catalog_category_product_index'); + + $catalogCategoryProductDimension = new Dimension( + \Magento\Store\Model\Store::ENTITY, + $this->storeManager->getStore()->getId() + ); + + $tableName = $this->tableResolver->resolve( + AbstractAction::MAIN_INDEX_TABLE, + [ + $catalogCategoryProductDimension + ] + ); $mainTableAlias = $this->extractTableAliasFromSelect($select); $select->joinInner( diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php index 7e992273a68dc..c11fb64cde7e6 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -93,6 +93,11 @@ class IndexTest extends \PHPUnit\Framework\TestCase */ protected $storeInterface; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $tableResolver; + /** * Setup * @@ -210,6 +215,30 @@ protected function setUp() ->willReturn('entity_id'); $objectManager = new ObjectManagerHelper($this); + + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->setMethods([ + 'getConnection', + 'getTableName' + ]) + ->disableOriginalConstructor() + ->getMock(); + $resource->expects($this->any()) + ->method('getConnection') + ->willReturn($connection); + $resource->expects($this->any())->method('getTableName')->willReturnArgument(0); + + $this->tableResolver = $objectManager->getObject( + \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver::class, + [ + 'resource' => $resource + ] + ); + $this->model = $objectManager->getObject( \Magento\Elasticsearch\Model\ResourceModel\Index::class, [ @@ -220,6 +249,7 @@ protected function setUp() 'categoryRepository' => $this->categoryRepository, 'eavConfig' => $this->eavConfig, 'connectionName' => 'default', + 'tableResolver' => $this->tableResolver ] ); } @@ -318,7 +348,7 @@ public function testGetCategoryProductIndexData() $select->expects($this->any()) ->method('from') ->with( - [null], + ['catalog_category_product_index_store1'], ['category_id', 'product_id', 'position', 'store_id'] )->willReturnSelf(); @@ -498,7 +528,7 @@ public function testGetFullCategoryProductIndexData() $this->assertInternalType( 'array', - $this->model->getFullCategoryProductIndexData([1, [1, ]]) + $this->model->getFullCategoryProductIndexData(1, [1, ]) ); } From 835c1e846cfc30c6b827f3f5c81dc55bf2af2f42 Mon Sep 17 00:00:00 2001 From: Sergii Kovalenko Date: Thu, 3 May 2018 17:28:26 +0300 Subject: [PATCH 092/236] MAGETWO-90957: Product generation fails on medium profile --- .../Magento/Setup/Model/FixtureGenerator/ProductGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php index b8c15f09faf7a..dab7a520d0ebd 100644 --- a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php @@ -171,7 +171,7 @@ public function generate($products, $fixtureMap) ], ]; $websiteIdsFixtures = $fixtureMap['website_ids'](1, 0); - if ((is_array($websiteIdsFixtures) && count($websiteIdsFixtures) === 1) || $websiteIdsFixtures != null) { + if ($websiteIdsFixtures !== null && is_array($websiteIdsFixtures) && count($websiteIdsFixtures) === 1) { // Get website id from fixture in case when one site is assigned per product $customTableMap['catalog_product_website'] = [ 'fields' => [ From eac772a584bc384dc24ad217d7964a33e0203129 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Thu, 3 May 2018 10:18:58 -0500 Subject: [PATCH 093/236] DEVOPS-2174: Fix integration tests --- .../testsuite/Magento/Framework/TranslateCachingTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php b/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php index b05d98133980b..f1f65ec2ada80 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/TranslateCachingTest.php @@ -48,7 +48,7 @@ public function testLoadDataCaching() /** @var \Magento\Framework\Translate $model */ $model = $this->objectManager->get(\Magento\Framework\Translate::class); - $model->loadData(\Magento\Framework\App\Area::AREA_FRONTEND); // this is supposed to cache the fixture + $model->loadData(\Magento\Framework\App\Area::AREA_FRONTEND, true); // this is supposed to cache the fixture $this->assertEquals('Fixture Db Translation', new Phrase('Fixture String')); /** @var \Magento\Translation\Model\ResourceModel\StringUtils $translateString */ From a4242e16620c0746599b0bf8a8ea9971a7af69a8 Mon Sep 17 00:00:00 2001 From: Sergii Kovalenko Date: Thu, 3 May 2018 18:49:14 +0300 Subject: [PATCH 094/236] MAGETWO-90957: Product generation fails on medium profile --- .../Model/FixtureGenerator/ProductGeneratorTest.php | 13 ++++++++++++- .../Model/FixtureGenerator/ProductGenerator.php | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/FixtureGenerator/ProductGeneratorTest.php b/dev/tests/integration/testsuite/Magento/Setup/Model/FixtureGenerator/ProductGeneratorTest.php index c2724fe8c61d5..ca1cb93eeef18 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Model/FixtureGenerator/ProductGeneratorTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/FixtureGenerator/ProductGeneratorTest.php @@ -9,11 +9,13 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Indexer\Model\Config; +use Magento\Store\Model\WebsiteRepository; use Magento\TestFramework\Helper\Bootstrap; /** * @magentoAppArea adminhtml * @magentoDataFixture Magento/Catalog/_files/category.php + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php */ class ProductGeneratorTest extends \PHPUnit\Framework\TestCase { @@ -37,6 +39,11 @@ class ProductGeneratorTest extends \PHPUnit\Framework\TestCase */ private $indexersState = []; + /** + * @var WebsiteRepository + */ + private $websiteRepository; + /** * @return void */ @@ -45,7 +52,7 @@ protected function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); $this->productGenerator = $this->objectManager->get(ProductGenerator::class); - + $this->websiteRepository = $this->objectManager->get(WebsiteRepository::class); $indexerRegistry = $this->objectManager->get(IndexerRegistry::class); $indexerListIds = $this->objectManager->get(Config::class)->getIndexers(); @@ -79,6 +86,7 @@ public function testProductGeneration() $price = 7.99; $url = 'simple-product-url'; $categoryId = 333; + $secondWebsiteId = $this->websiteRepository->get('test')->getId(); $fixtureMap = [ 'name' => function () use ($name) { @@ -96,6 +104,9 @@ public function testProductGeneration() 'category_ids' => function () use ($categoryId) { return $categoryId; }, + 'website_ids' => function () use ($secondWebsiteId) { + return [1, $secondWebsiteId]; + } ]; $this->productGenerator->generate(1, $fixtureMap); diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php index dab7a520d0ebd..661d6bfc94048 100644 --- a/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php +++ b/setup/src/Magento/Setup/Model/FixtureGenerator/ProductGenerator.php @@ -171,7 +171,7 @@ public function generate($products, $fixtureMap) ], ]; $websiteIdsFixtures = $fixtureMap['website_ids'](1, 0); - if ($websiteIdsFixtures !== null && is_array($websiteIdsFixtures) && count($websiteIdsFixtures) === 1) { + if (is_array($websiteIdsFixtures) && count($websiteIdsFixtures) === 1) { // Get website id from fixture in case when one site is assigned per product $customTableMap['catalog_product_website'] = [ 'fields' => [ From 3a12905324c8e4975e554eeeec41552627e4369c Mon Sep 17 00:00:00 2001 From: Danny Verkade Date: Thu, 3 May 2018 20:26:06 +0200 Subject: [PATCH 095/236] Fix for displaying a negative price for a custom option. Currently a negative price is displayed as +- 5.00 for instance. By changing the template to check of the value is actually positive a negative value will be displayed as - 5.00 and a positive value will be displayed as + 5.00 --- app/code/Magento/Catalog/view/base/web/js/price-options.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/view/base/web/js/price-options.js b/app/code/Magento/Catalog/view/base/web/js/price-options.js index ceeea4c878622..e18abe3af38a6 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-options.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-options.js @@ -20,8 +20,10 @@ define([ optionConfig: {}, optionHandlers: {}, optionTemplate: '<%= data.label %>' + - '<% if (data.finalPrice.value) { %>' + + '<% if (data.finalPrice.value > 0) { %>' + ' +<%- data.finalPrice.formatted %>' + + '<% } else if (data.finalPrice.value < 0) { %>' + + ' <%- data.finalPrice.formatted %>' + '<% } %>', controlContainer: 'dd' }; From 8a038ca43f0ab2d92831981e435f8e457a7bca40 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Fri, 4 May 2018 09:33:53 +0300 Subject: [PATCH 096/236] MAGETWO-90388: [FAT] Fix UpgradeSystemTest to correctly work with other components list --- .../Setup/Test/Constraint/AssertVersionAndEditionCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php index 057147586734e..91a29734d2440 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php @@ -30,7 +30,7 @@ public function processAssert(SetupWizard $setupWizard, array $upgrade) } } $actualMessage = $setupWizard->getSystemUpgrade()->getUpgradeMessage(); - \PHPUnit_Framework_Assert::assertContains( + \PHPUnit\Framework\Assert::assertContains( $message, $actualMessage, "Updater application check is incorrect: \n" From 585906bd00961f1c0443a590a385f0d0c158f53f Mon Sep 17 00:00:00 2001 From: Stanislav Lopukhov Date: Fri, 4 May 2018 09:13:15 +0300 Subject: [PATCH 097/236] MAGETWO-90563: [Forwardport] Implement segmentation for Category Product Indexer --- .../Category/Product/AbstractAction.php | 3 - .../Indexer/Category/Product/Action/Rows.php | 26 ++++--- .../Indexer/Product/Category/Action/Rows.php | 68 +++++++++++-------- .../ResourceModel/Product/Collection.php | 2 - 4 files changed, 54 insertions(+), 45 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php index 40f46453a5024..522283a0fbe44 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Catalog\Model\Indexer\Category\Product; use Magento\Catalog\Api\Data\ProductInterface; @@ -16,7 +14,6 @@ use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; -use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; /** * Class AbstractAction diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php index 248ec970d2250..3bd4910767587 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php @@ -36,17 +36,16 @@ public function execute(array $entityIds = [], $useTempTable = false) /** * Return array of all category root IDs + tree root ID * - * @return int[] + * @param \Magento\Store\Model\Store $store + * @return int */ - protected function getRootCategoryIds() + private function getRootCategoryId($store) { - $rootIds = [\Magento\Catalog\Model\Category::TREE_ROOT_ID]; - foreach ($this->storeManager->getStores() as $store) { - if ($this->getPathFromCategoryId($store->getRootCategoryId())) { - $rootIds[] = $store->getRootCategoryId(); - } + $rootId = \Magento\Catalog\Model\Category::TREE_ROOT_ID; + if ($this->getPathFromCategoryId($store->getRootCategoryId())) { + $rootId = $store->getRootCategoryId(); } - return $rootIds; + return $rootId; } /** @@ -54,10 +53,15 @@ protected function getRootCategoryIds() * * @return void */ - protected function removeEntries() + private function removeEntries() { - $removalCategoryIds = array_diff($this->limitationByCategories, $this->getRootCategoryIds()); - $this->connection->delete($this->getMainTable(), ['category_id IN (?)' => $removalCategoryIds]); + foreach ($this->storeManager->getStores() as $store) { + $removalCategoryIds = array_diff($this->limitationByCategories, [$this->getRootCategoryId($store)]); + $this->connection->delete( + $this->getIndexTable($store->getId()), + ['category_id IN (?)' => $removalCategoryIds] + ); + } } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index b67660a24ef88..182f04de4ab0e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -6,12 +6,17 @@ namespace Magento\Catalog\Model\Indexer\Product\Category\Action; use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Config; use Magento\Catalog\Model\Product; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Query\Generator as QueryGenerator; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Event\ManagerInterface as EventManagerInterface; use Magento\Framework\Indexer\CacheContext; +use Magento\Store\Model\StoreManagerInterface; /** - * Reindex products categories. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction @@ -29,32 +34,31 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio private $cacheContext; /** - * @var \Magento\Framework\Event\ManagerInterface|null + * @var EventManagerInterface|null */ private $eventManager; /** - * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Model\Config $config - * @param \Magento\Framework\DB\Query\Generator|null $queryGenerator - * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool + * @param ResourceConnection $resource + * @param StoreManagerInterface $storeManager + * @param Config $config + * @param QueryGenerator|null $queryGenerator + * @param MetadataPool|null $metadataPool * @param CacheContext|null $cacheContext - * @param \Magento\Framework\Event\ManagerInterface|null $eventManager + * @param EventManagerInterface|null $eventManager */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Config $config, - \Magento\Framework\DB\Query\Generator $queryGenerator = null, - \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, + ResourceConnection $resource, + StoreManagerInterface $storeManager, + Config $config, + QueryGenerator $queryGenerator = null, + MetadataPool $metadataPool = null, CacheContext $cacheContext = null, - \Magento\Framework\Event\ManagerInterface $eventManager = null + EventManagerInterface $eventManager = null ) { parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool); $this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class); - $this->eventManager = $eventManager ?: - ObjectManager::getInstance()->get(\Magento\Framework\Event\ManagerInterface::class); + $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class); } /** @@ -152,10 +156,12 @@ private function registerCategories(array $categoryIds) */ protected function removeEntries() { - $this->connection->delete( - $this->getMainTable(), - ['product_id IN (?)' => $this->limitationByProducts] - ); + foreach ($this->storeManager->getStores() as $store) { + $this->connection->delete( + $this->getIndexTable($store->getId()), + ['product_id IN (?)' => $this->limitationByProducts] + ); + }; } /** @@ -167,7 +173,6 @@ protected function removeEntries() protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store) { $select = parent::getNonAnchorCategoriesSelect($store); - return $select->where('ccp.product_id IN (?) OR relation.child_id IN (?)', $this->limitationByProducts); } @@ -206,19 +211,24 @@ protected function isRangingNeeded() } /** - * Returns a list of category ids which are assigned to product ids in the index. + * Returns a list of category ids which are assigned to product ids in the index * - * @param array $productIds * @return \Magento\Framework\Indexer\CacheContext */ private function getCategoryIdsFromIndex(array $productIds) { - $categoryIds = $this->connection->fetchCol( - $this->connection->select() - ->from($this->getMainTable(), ['category_id']) - ->where('product_id IN (?)', $productIds) - ->distinct() - ); + $categoryIds = []; + foreach ($this->storeManager->getStores() as $store) { + $categoryIds = array_merge( + $categoryIds, + $this->connection->fetchCol( + $this->connection->select() + ->from($this->getIndexTable($store->getId()), ['category_id']) + ->where('product_id IN (?)', $productIds) + ->distinct() + ) + ); + }; $parentCategories = $categoryIds; foreach ($categoryIds as $categoryId) { $parentIds = explode('/', $this->getPathFromCategoryId($categoryId)); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index a4530b6c47087..815ff7286c7f5 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; From b7f79fb857d7c7791093bc4aa318fc5842e7e4a2 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Fri, 4 May 2018 10:28:01 +0300 Subject: [PATCH 098/236] MAGETWO-90144: [Forwardport] Fixed amount discount for whole cart applying an extra cent to the discount amount for --- .../SalesRule/Model/Rule/Action/Discount/CartFixed.php | 4 ++-- .../SalesRule/Model/Rule/Action/Discount/CartFixedTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php index c53e23321b905..3cd776fe99f5d 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php +++ b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php @@ -42,9 +42,9 @@ public function __construct( Validator $validator, DataFactory $discountDataFactory, PriceCurrencyInterface $priceCurrency, - DeltaPriceRound $deltaPriceRound = null + DeltaPriceRound $deltaPriceRound ) { - $this->deltaPriceRound = $deltaPriceRound ?: ObjectManager::getInstance()->get(DeltaPriceRound::class); + $this->deltaPriceRound = $deltaPriceRound; parent::__construct($validator, $discountDataFactory, $priceCurrency); } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php index 0dc245bd7e6e6..e601e2dd59232 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php @@ -105,7 +105,7 @@ private function createProduct(float $price): ProductInterface $productRepository = Bootstrap::getObjectManager()->get(ProductRepository::class); $product = Bootstrap::getObjectManager()->create(Product::class); $product->setTypeId('simple') - ->setAttributeSetId(4) + ->setAttributeSetId($product->getDefaultAttributeSetId()) ->setWebsiteIds([1]) ->setName($name) ->setSku(uniqid($name)) From e2ae3b63a732de96741d6357615e86feda214e2e Mon Sep 17 00:00:00 2001 From: Stanislav Lopukhov Date: Fri, 4 May 2018 10:08:41 +0300 Subject: [PATCH 099/236] MAGETWO-90563: [Forwardport] Implement segmentation for Category Product Indexer --- .../ResourceModel/Product/Collection.php | 5 +- .../Magento/Catalog/Model/CategoryTest.php | 2 +- .../Import/Product/Type/ConfigurableTest.php | 71 ++++++++++--------- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 815ff7286c7f5..0ce67a96a99cc 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -476,7 +476,10 @@ public function isEnabledFlat() protected function _construct() { if ($this->isEnabledFlat()) { - $this->_init(\Magento\Catalog\Model\Product::class, \Magento\Catalog\Model\ResourceModel\Product\Flat::class); + $this->_init( + \Magento\Catalog\Model\Product::class, + \Magento\Catalog\Model\ResourceModel\Product\Flat::class + ); } else { $this->_init(\Magento\Catalog\Model\Product::class, \Magento\Catalog\Model\ResourceModel\Product::class); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php index cd312f147c7c7..1d7936d740b8d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php @@ -323,7 +323,7 @@ public function testDeleteChildren() /** * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/Catalog/_files/categories.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @return void */ public function testCreateSubcategoryWithMultipleStores() diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php index c39e03773c356..04769401c147e 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Import/Product/Type/ConfigurableTest.php @@ -152,18 +152,19 @@ public function testConfigurableImport($pathToFile, $productName, $optionSkuList } /** + * @magentoDataFixture Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php * @magentoAppArea adminhtml * @magentoAppIsolation enabled - * @return void + * @magentoDbIsolation disabled */ public function testConfigurableImportWithMultipleStores() { $productSku = 'Configurable 1'; $products = [ 'default' => 'Configurable 1', - 'fixture_second_store' => 'Configurable 1 Second Store', + 'fixture_second_store' => 'Configurable 1 Second Store' ]; $filesystem = $this->objectManager->create( \Magento\Framework\Filesystem::class @@ -174,24 +175,26 @@ public function testConfigurableImportWithMultipleStores() \Magento\ImportExport\Model\Import\Source\Csv::class, [ 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views.csv', - 'directory' => $directory, + 'directory' => $directory ] ); - $errors = $this->model->setSource($source)->setParameters( + $errors = $this->model->setSource( + $source + )->setParameters( [ 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product', + 'entity' => 'catalog_product' ] )->validateData(); - $this->assertTrue($errors->getErrorsCount() === 0); + $this->assertTrue($errors->getErrorsCount() == 0); $this->model->importData(); foreach ($products as $storeCode => $productName) { $store = $this->objectManager->create(\Magento\Store\Model\Store::class); $store->load($storeCode, 'code'); - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ $product = $productRepository->get($productSku, 0, $store->getId()); $this->assertFalse($product->isObjectNew()); @@ -201,36 +204,40 @@ public function testConfigurableImportWithMultipleStores() } /** + * @magentoDataFixture Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php * @magentoDbIsolation disabled * @magentoAppArea adminhtml - * @return void */ public function testConfigurableImportWithStoreSpecifiedMainItem() { - $expectedErrorMessage = 'Product with assigned super attributes should not have specified "store_view_code"' - . ' value'; - $filesystem = $this->objectManager->create( - \Magento\Framework\Filesystem::class - ); - - $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); - $source = $this->objectManager->create( - \Magento\ImportExport\Model\Import\Source\Csv::class, - [ - 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views_error.csv', - 'directory' => $directory, - ] - ); - $errors = $this->model->setSource($source)->setParameters( - [ - 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, - 'entity' => 'catalog_product', - ] - )->validateData(); - - $this->assertTrue($errors->getErrorsCount() === 1); - $this->assertEquals($expectedErrorMessage, $errors->getAllErrors()[0]->getErrorMessage()); + { + $expectedErrorMessage = 'Product with assigned super attributes should not have specified "store_view_code"' + . ' value'; + $filesystem = $this->objectManager->create( + \Magento\Framework\Filesystem::class + ); + + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/../../_files/import_configurable_for_multiple_store_views_error.csv', + 'directory' => $directory + ] + ); + $errors = $this->model->setSource( + $source + )->setParameters( + [ + 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, + 'entity' => 'catalog_product' + ] + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 1); + $this->assertEquals($expectedErrorMessage, $errors->getAllErrors()[0]->getErrorMessage()); + } } } From b3e036033e63973196d046e677388c69957b2d89 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Fri, 4 May 2018 16:58:50 +0300 Subject: [PATCH 100/236] MAGETWO-90294: 12526: Currency change, Bank Transfer but checkout page shows "Your credit card will be charged for" #993 --- app/code/Magento/Tax/i18n/en_US.csv | 3 ++- .../Magento/Tax/view/frontend/layout/checkout_index_index.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Tax/i18n/en_US.csv b/app/code/Magento/Tax/i18n/en_US.csv index 2314f27b92928..e6d89deb7696c 100644 --- a/app/code/Magento/Tax/i18n/en_US.csv +++ b/app/code/Magento/Tax/i18n/en_US.csv @@ -176,4 +176,5 @@ Rate,Rate "Order Total Incl. Tax","Order Total Incl. Tax" "Order Total","Order Total" "Your credit card will be charged for","Your credit card will be charged for" -"An error occurred while loading tax rates.","An error occurred while loading tax rates." \ No newline at end of file +"An error occurred while loading tax rates.","An error occurred while loading tax rates." +"You will be charged for","You will be charged for" diff --git a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml index 41afddb8fa4e1..8f60ba15d8a1a 100644 --- a/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Tax/view/frontend/layout/checkout_index_index.xml @@ -70,7 +70,7 @@ Order Total Excl. Tax Order Total Incl. Tax - Your credit card will be charged for + You will be charged for Order Total From 4091835d49d852cdbf181bee52284688be3a427a Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Fri, 4 May 2018 17:21:48 +0300 Subject: [PATCH 101/236] MAGETWO-90293: 12468: Sort by Price not working on CatalogSearch Page in Magento 2 #929 --- app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php index 46080ab5c3330..c53691ecada24 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php @@ -689,7 +689,7 @@ public function getWidgetOptionsJson(array $customOptions = []) 'limit' => ToolbarModel::LIMIT_PARAM_NAME, 'modeDefault' => $defaultMode, 'directionDefault' => $this->_direction ?: ProductList::DEFAULT_SORT_DIRECTION, - 'orderDefault' => $this->_productListHelper->getDefaultSortField(), + 'orderDefault' => $this->getOrderField(), 'limitDefault' => $this->_productListHelper->getDefaultLimitPerPageValue($defaultMode), 'url' => $this->getPagerUrl(), ]; From 0fe7f85e2c973954d59108be4627e9a5978bf434 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Fri, 4 May 2018 18:17:47 +0300 Subject: [PATCH 102/236] MAGETWO-90388: [FAT] Fix UpgradeSystemTest to correctly work with other components list --- .../Magento/Setup/Test/Block/Readiness.php | 2 +- .../Setup/Test/Block/SelectVersion.php | 6 ++++-- .../SelectVersion/OtherComponentsGrid.php | 21 ++++++++++++++----- .../OtherComponentsGrid/Item.php | 2 +- .../AssertVersionAndEditionCheck.php | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php index d48c5f474f26a..3aa6fc8f2a84f 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/Readiness.php @@ -195,7 +195,7 @@ public function getDependencyCheck() /** * @return bool */ - public function isPhpVersionCheckVisible() + public function isPhpVersionCheckVisible() : bool { return $this->_rootElement->find($this->phpVersionCheck)->isVisible(); } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php index f9f018ad099d9..5cb71d85a51ce 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion.php @@ -105,7 +105,7 @@ private function chooseShowAllVersions() * @param array $packages * @return void */ - public function chooseUpgradeOtherComponents(array $packages) + public function chooseUpgradeOtherComponents(array $packages) :void { $this->_rootElement->find("[for=yesUpdateComponents]")->click(); $this->waitForElementNotVisible("[ng-show=\"!componentsProcessed\""); @@ -125,6 +125,7 @@ public function chooseUpgradeOtherComponents(array $packages) public function isComponentsEmpty() { $this->waitForElementVisible($this->waitEmpty, Locator::SELECTOR_XPATH); + return $this->_rootElement->find($this->empty)->isVisible(); } @@ -143,7 +144,7 @@ public function getSelectedPackages() * * @return OtherComponentsGrid */ - private function getOtherComponentsGrid() + private function getOtherComponentsGrid() : OtherComponentsGrid { if (!isset($this->otherComponentGrid)) { $this->otherComponentGrid = $this->blockFactory->create( @@ -151,6 +152,7 @@ private function getOtherComponentsGrid() ['element' => $this->_rootElement->find($this->otherComponentsGrid)] ); } + return $this->otherComponentGrid; } } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php index 7b34167c0401f..8ca2c0654e28c 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid.php @@ -12,6 +12,9 @@ use Magento\Mtf\Client\Locator; use Magento\Setup\Test\Block\SelectVersion\OtherComponentsGrid\Item; +/** + * Perform OtherComponentsGrid block. + */ class OtherComponentsGrid extends Block { /** @@ -30,9 +33,12 @@ class OtherComponentsGrid extends Block private $selectedPackages = []; /** - * @param $packages + * Set version of the packages. + * + * @param array $packages + * @return void */ - public function setVersions(array $packages) + public function setVersions(array $packages) : void { foreach ($packages as $package) { $selector = sprintf($this->itemComponent, $package['name']); @@ -50,24 +56,29 @@ public function setVersions(array $packages) * * @return array */ - public function getSelectedPackages() + public function getSelectedPackages() : array { return $this->selectedPackages; } /** + * Set pager size. + * * @param int $count + * @return void */ - public function setItemsPerPage($count) + public function setItemsPerPage(int $count) : void { $this->_rootElement->find($this->perPage, Locator::SELECTOR_CSS, 'select')->setValue($count); } /** + * Get component block. + * * @param ElementInterface $element * @return Item */ - private function getComponentRow($element) + private function getComponentRow(ElementInterface $element) : Item { return $this->blockFactory->create( Item::class, diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php index 576eccb487f82..8c24323f37618 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Block/SelectVersion/OtherComponentsGrid/Item.php @@ -34,7 +34,7 @@ class Item extends Block * * @param string $version */ - public function setVersion($version) + public function setVersion(string $version) { $this->_rootElement->find($this->version, Locator::SELECTOR_CSS, 'select')->setValue($version); } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php index 91a29734d2440..caafa814a871a 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertVersionAndEditionCheck.php @@ -21,7 +21,7 @@ class AssertVersionAndEditionCheck extends AbstractConstraint * @param array $upgrade * @return void */ - public function processAssert(SetupWizard $setupWizard, array $upgrade) + public function processAssert(SetupWizard $setupWizard, array $upgrade) :void { $message = "We're ready to upgrade {$upgrade['package']} to {$upgrade['version']}."; if ($upgrade['otherComponents'] === 'Yes' && isset($upgrade['selectedPackages'])) { From 33e4a1dc748b60d3327acc21675fc131a161327b Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 4 May 2018 10:21:55 -0500 Subject: [PATCH 103/236] DEVOPS-2174: Fix static tests --- .../Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index 19b0f06c31784..8ca2e08ea683b 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -19,6 +19,7 @@ * Tests the different cases of consumers running by ConsumersRunner * * {@inheritdoc} + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ConsumersRunnerTest extends \PHPUnit\Framework\TestCase { From 54534022b78a55d1cc3d4b38bd64226f2efcd0e8 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Fri, 4 May 2018 18:36:15 +0300 Subject: [PATCH 104/236] MAGETWO-75125: Unable to place order on environment with split database within PayPal Express Checkout --- app/code/Magento/Quote/Model/Quote.php | 14 ++- .../Quote/Model/ResourceModel/Quote.php | 23 ---- .../Quote/Test/Unit/Model/QuoteTest.php | 27 ++-- .../Unit/Model/ResourceModel/QuoteTest.php | 115 +++--------------- .../Sales/Model/OrderIncrementIdChecker.php | 52 ++++++++ .../Model/OrderIncrementIdCheckerTest.php | 81 ++++++++++++ .../Quote/Model/ResourceModel/QuoteTest.php | 45 ------- .../Model/OrderIncrementIdCheckerTest.php | 51 ++++++++ .../Test/Legacy/_files/obsolete_methods.php | 3 +- 9 files changed, 232 insertions(+), 179 deletions(-) create mode 100644 app/code/Magento/Sales/Model/OrderIncrementIdChecker.php create mode 100644 app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 84034adfe9f22..ad90b75cbb0d5 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -14,6 +14,7 @@ use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Total as AddressTotal; use Magento\Sales\Model\Status; +use Magento\Framework\App\ObjectManager; /** * Quote model @@ -353,6 +354,11 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C */ protected $shippingAddressesItems; + /** + * @var \Magento\Sales\Model\OrderIncrementIdChecker + */ + private $orderIncrementIdChecker; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -394,6 +400,7 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data + * @param \Magento\Sales\Model\OrderIncrementIdChecker|null $orderIncrementIdChecker * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -436,7 +443,8 @@ public function __construct( \Magento\Quote\Model\ShippingAssignmentFactory $shippingAssignmentFactory, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + \Magento\Sales\Model\OrderIncrementIdChecker $orderIncrementIdChecker = null ) { $this->quoteValidator = $quoteValidator; $this->_catalogProduct = $catalogProduct; @@ -471,6 +479,8 @@ public function __construct( $this->totalsReader = $totalsReader; $this->shippingFactory = $shippingFactory; $this->shippingAssignmentFactory = $shippingAssignmentFactory; + $this->orderIncrementIdChecker = $orderIncrementIdChecker ?: ObjectManager::getInstance() + ->get(\Magento\Sales\Model\OrderIncrementIdChecker::class); parent::__construct( $context, $registry, @@ -2184,7 +2194,7 @@ public function reserveOrderId() } else { //checking if reserved order id was already used for some order //if yes reserving new one if not using old one - if ($this->_getResource()->isOrderIncrementIdUsed($this->getReservedOrderId())) { + if ($this->orderIncrementIdChecker->isIncrementIdUsed($this->getReservedOrderId())) { $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); } } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php index 571f1b4686bc1..946c0e0c5f3b8 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php @@ -170,29 +170,6 @@ public function getReservedOrderId($quote) ->getNextValue(); } - /** - * Check if order increment ID is already used. - * Method can be used to avoid collisions of order IDs. - * - * @param int $orderIncrementId - * @return bool - */ - public function isOrderIncrementIdUsed($orderIncrementId) - { - /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ - $adapter = $this->_resources->getConnection('sales'); - $bind = [':increment_id' => $orderIncrementId]; - /** @var \Magento\Framework\DB\Select $select */ - $select = $adapter->select(); - $select->from($this->getTable('sales_order'), 'entity_id')->where('increment_id = :increment_id'); - $entity_id = $adapter->fetchOne($select, $bind); - if ($entity_id > 0) { - return true; - } - - return false; - } - /** * Mark quotes - that depend on catalog price rules - to be recollected on demand * diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php index 451382abb4684..a92d360bad35b 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php @@ -145,6 +145,11 @@ class QuoteTest extends \PHPUnit\Framework\TestCase */ private $itemProcessor; + /** + * @var \Magento\Sales\Model\OrderIncrementIdChecker|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderIncrementIdChecker; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -256,6 +261,7 @@ protected function setUp() \Magento\Customer\Api\Data\CustomerInterfaceFactory::class, ['create'] ); + $this->orderIncrementIdChecker = $this->createMock(\Magento\Sales\Model\OrderIncrementIdChecker::class); $this->quote = (new ObjectManager($this)) ->getObject( \Magento\Quote\Model\Quote::class, @@ -280,9 +286,10 @@ protected function setUp() 'extensionAttributesJoinProcessor' => $this->extensionAttributesJoinProcessorMock, 'customerDataFactory' => $this->customerDataFactoryMock, 'itemProcessor' => $this->itemProcessor, + 'orderIncrementIdChecker' => $this->orderIncrementIdChecker, 'data' => [ - 'reserved_order_id' => 1000001 - ] + 'reserved_order_id' => 1000001, + ], ] ); } @@ -1222,28 +1229,32 @@ public function testGetAllItems() } /** - * Test to verify if existing reserved_order_id in use + * Test to verify if existing reserved_order_id in use. * * @param bool $isReservedOrderIdExist * @param int $reservedOrderId + * @return void * @dataProvider reservedOrderIdDataProvider */ - public function testReserveOrderId($isReservedOrderIdExist, $reservedOrderId) + public function testReserveOrderId(bool $isReservedOrderIdExist, int $reservedOrderId): void { - $this->resourceMock + $this->orderIncrementIdChecker ->expects($this->once()) - ->method('isOrderIncrementIdUsed') + ->method('isIncrementIdUsed') ->with(1000001)->willReturn($isReservedOrderIdExist); $this->resourceMock->expects($this->any())->method('getReservedOrderId')->willReturn($reservedOrderId); $this->quote->reserveOrderId(); $this->assertEquals($reservedOrderId, $this->quote->getReservedOrderId()); } - public function reservedOrderIdDataProvider() + /** + * @return array + */ + public function reservedOrderIdDataProvider(): array { return [ 'id_already_in_use' => [true, 100002], - 'id_not_in_use' => [false, 1000001] + 'id_not_in_use' => [false, 1000001], ]; } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php index a121cd55a98b4..7136e8260a880 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php @@ -6,35 +6,17 @@ namespace Magento\Quote\Test\Unit\Model\ResourceModel; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\DB\Adapter\Pdo\Mysql; -use Magento\Framework\DB\Select; use Magento\Framework\DB\Sequence\SequenceInterface; -use Magento\Framework\Model\ResourceModel\Db\Context; -use Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite; -use Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Model\Quote; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\SalesSequence\Model\Manager; +/** + * Unit test for \Magento\Quote\Model\ResourceModel\Quote. + */ class QuoteTest extends \PHPUnit\Framework\TestCase { - /** - * @var ResourceConnection - */ - private $resourceMock; - - /** - * @var Mysql - */ - private $adapterMock; - - /** - * @var Select - */ - private $selectMock; - /** * @var Quote|\PHPUnit_Framework_MockObject_MockObject */ @@ -55,98 +37,31 @@ class QuoteTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * @inheritdoc + */ protected function setUp() { $objectManagerHelper = new ObjectManager($this); - $this->selectMock = $this->getMockBuilder(Select::class) - ->disableOriginalConstructor() - ->getMock(); - $this->selectMock->expects($this->any())->method('from')->will($this->returnSelf()); - $this->selectMock->expects($this->any())->method('where'); - - $this->adapterMock = $this->getMockBuilder(Mysql::class) - ->disableOriginalConstructor() - ->getMock(); - $this->adapterMock->expects($this->any())->method('select')->will($this->returnValue($this->selectMock)); - - $this->resourceMock = $this->getMockBuilder(ResourceConnection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resourceMock->expects( - $this->any() - )->method( - 'getConnection' - )->will( - $this->returnValue($this->adapterMock) - ); - - $context = $this->getMockBuilder(Context::class) - ->disableOriginalConstructor() - ->getMock(); - $context->expects( - $this->once() - )->method( - 'getResources' - )->will( - $this->returnValue($this->resourceMock) - ); - - $snapshot = $this->getMockBuilder(Snapshot::class) - ->disableOriginalConstructor() - ->getMock(); - $relationComposite = $this->getMockBuilder(RelationComposite::class) - ->disableOriginalConstructor() - ->getMock(); - $this->quoteMock = $this->getMockBuilder(Quote::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sequenceManagerMock = $this->getMockBuilder(Manager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sequenceMock = $this->getMockBuilder(SequenceInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->quoteMock = $this->createMock(Quote::class); + $this->sequenceManagerMock = $this->createMock(Manager::class); + $this->sequenceMock = $this->createMock(SequenceInterface::class); $this->model = $objectManagerHelper->getObject( QuoteResource::class, [ - 'context' => $context, - 'entitySnapshot' => $snapshot, - 'entityRelationComposite' => $relationComposite, 'sequenceManager' => $this->sequenceManagerMock, - 'connectionName' => null, ] ); } /** - * Unit test to verify if isOrderIncrementIdUsed method works with different types increment ids - * - * @param array $value - * @dataProvider isOrderIncrementIdUsedDataProvider - */ - public function testIsOrderIncrementIdUsed($value) - { - $expectedBind = [':increment_id' => $value]; - $this->adapterMock->expects($this->once())->method('fetchOne')->with($this->selectMock, $expectedBind); - $this->model->isOrderIncrementIdUsed($value); - } - - /** - * @return array - */ - public function isOrderIncrementIdUsedDataProvider() - { - return [[100000001], ['10000000001'], ['M10000000001']]; - } - - /** - * /** - * @param $entityType - * @param $storeId - * @param $reservedOrderId + * @param string $entityType + * @param int $storeId + * @param string $reservedOrderId + * @return void * @dataProvider getReservedOrderIdDataProvider */ - public function testGetReservedOrderId($entityType, $storeId, $reservedOrderId) + public function testGetReservedOrderId(string $entityType, int $storeId, string $reservedOrderId): void { $this->sequenceManagerMock->expects($this->once()) ->method('getSequence') @@ -170,7 +85,7 @@ public function getReservedOrderIdDataProvider(): array return [ [\Magento\Sales\Model\Order::ENTITY, 1, '1000000001'], [\Magento\Sales\Model\Order::ENTITY, 2, '2000000001'], - [\Magento\Sales\Model\Order::ENTITY, 3, '3000000001'] + [\Magento\Sales\Model\Order::ENTITY, 3, '3000000001'], ]; } } diff --git a/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php b/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php new file mode 100644 index 0000000000000..0af61835a9c9b --- /dev/null +++ b/app/code/Magento/Sales/Model/OrderIncrementIdChecker.php @@ -0,0 +1,52 @@ +resourceModel = $resourceModel; + } + + /** + * Check if order increment ID is already used. + * + * Method can be used to avoid collisions of order IDs. + * + * @param string|int $orderIncrementId + * @return bool + */ + public function isIncrementIdUsed($orderIncrementId): bool + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ + $adapter = $this->resourceModel->getConnection(); + $bind = [':increment_id' => $orderIncrementId]; + /** @var \Magento\Framework\DB\Select $select */ + $select = $adapter->select(); + $select->from($this->resourceModel->getMainTable(), $this->resourceModel->getIdFieldName()) + ->where('increment_id = :increment_id'); + $entity_id = $adapter->fetchOne($select, $bind); + if ($entity_id > 0) { + return true; + } + + return false; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php new file mode 100644 index 0000000000000..e53cb7bfdf8c6 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderIncrementIdCheckerTest.php @@ -0,0 +1,81 @@ +selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $this->selectMock->expects($this->any())->method('from')->will($this->returnSelf()); + $this->selectMock->expects($this->any())->method('where'); + + $this->adapterMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class); + $this->adapterMock->expects($this->any())->method('select')->will($this->returnValue($this->selectMock)); + + $this->resourceMock = $this->createMock(\Magento\Sales\Model\ResourceModel\Order::class); + $this->resourceMock->expects($this->any())->method('getConnection')->willReturn($this->adapterMock); + + $this->model = $objectManagerHelper->getObject( + \Magento\Sales\Model\OrderIncrementIdChecker::class, + [ + 'resourceModel' => $this->resourceMock, + ] + ); + } + + /** + * Unit test to verify if isOrderIncrementIdUsed method works with different types increment ids. + * + * @param string|int $value + * @return void + * @dataProvider isOrderIncrementIdUsedDataProvider + */ + public function testIsIncrementIdUsed($value): void + { + $expectedBind = [':increment_id' => $value]; + $this->adapterMock->expects($this->once())->method('fetchOne')->with($this->selectMock, $expectedBind); + $this->model->isIncrementIdUsed($value); + } + + /** + * @return array + */ + public function isOrderIncrementIdUsedDataProvider(): array + { + return [[100000001], ['10000000001'], ['M10000000001']]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php deleted file mode 100644 index 3bd22ef29cb23..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php +++ /dev/null @@ -1,45 +0,0 @@ -_resourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\ResourceModel\Quote::class - ); - } - - /** - * Test to verify if isOrderIncrementIdUsed method works with numeric increment ids - * - * @magentoDataFixture Magento/Sales/_files/order.php - */ - public function testIsOrderIncrementIdUsedNumericIncrementId() - { - $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('100000001')); - } - - /** - * Test to verify if isOrderIncrementIdUsed method works with alphanumeric increment ids - * - * @magentoDataFixture Magento/Sales/_files/order_alphanumeric_id.php - */ - public function testIsOrderIncrementIdUsedAlphanumericIncrementId() - { - $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('M00000001')); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php new file mode 100644 index 0000000000000..6111c73038f7c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/OrderIncrementIdCheckerTest.php @@ -0,0 +1,51 @@ +checker = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Sales\Model\OrderIncrementIdChecker::class + ); + } + + /** + * Test to verify if isIncrementIdUsed method works with numeric increment ids. + * + * @magentoDataFixture Magento/Sales/_files/order.php + * @return void + */ + public function testIsOrderIncrementIdUsedNumericIncrementId(): void + { + $this->assertTrue($this->checker->isIncrementIdUsed('100000001')); + } + + /** + * Test to verify if isIncrementIdUsed method works with alphanumeric increment ids. + * + * @magentoDataFixture Magento/Sales/_files/order_alphanumeric_id.php + * @return void + */ + public function testIsOrderIncrementIdUsedAlphanumericIncrementId(): void + { + $this->assertTrue($this->checker->isIncrementIdUsed('M00000001')); + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index ee91c34002f6c..8b811cc6b3fc0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -6,7 +6,7 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreStart +// @codingStandardsIgnoreFile return [ ['__get', 'Magento\Framework\DataObject'], @@ -2567,4 +2567,5 @@ 'configure', 'Magento\Framework\MessageQueue\ConsumerInterface' ], + ['isOrderIncrementIdUsed', 'Magento\Quote\Model\ResourceModel\Quote', 'Magento\Sales\Model\OrderIncrementIdChecker::isIncrementIdUsed'], ]; From ef4446ec438b7635ae10c71fd8733ec16087a9bd Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Fri, 4 May 2018 11:20:53 -0500 Subject: [PATCH 105/236] MAGETWO-90182: Alter option saving mechanism - add integration tests --- .../Bundle/Model/Option/SaveAction.php | 159 ++++++++++++++++++ .../Magento/Bundle/Model/OptionRepository.php | 96 ++--------- .../Bundle/Model/Product/SaveHandler.php | 12 +- .../Bundle/Model/OptionRepositoryTest.php | 78 +++++++++ .../Bundle/_files/empty_bundle_product.php | 29 ++++ .../_files/empty_bundle_product_rollback.php | 23 +++ .../Magento/Bundle/_files/product.php | 16 +- setup/performance-toolkit/benchmark.jmx | 2 +- 8 files changed, 331 insertions(+), 84 deletions(-) create mode 100644 app/code/Magento/Bundle/Model/Option/SaveAction.php create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php new file mode 100644 index 0000000000000..cf893820b5179 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -0,0 +1,159 @@ +optionResource = $optionResource; + $this->metadataPool = $metadataPool; + $this->type = $type; + $this->linkManagement = $linkManagement; + } + + /** + * Manage the logic of saving a bundle option, including the coalescence of its parent product data. + * + * @param ProductInterface $bundleProduct + * @param OptionInterface $option + * @return OptionInterface + * @throws CouldNotSaveException + * @throws \Exception + */ + public function save(ProductInterface $bundleProduct, OptionInterface $option) + { + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + + $option->setStoreId($bundleProduct->getStoreId()); + $parentId = $bundleProduct->getData($metadata->getLinkField()); + $option->setParentId($parentId); + + $optionId = $option->getOptionId(); + $linksToAdd = []; + $optionCollection = $this->type->getOptionsCollection($bundleProduct); + $optionCollection->setIdFilter($option->getOptionId()); + $optionCollection->setProductLinkFilter($parentId); + + /** @var \Magento\Bundle\Model\Option $existingOption */ + $existingOption = $optionCollection->getFirstItem(); + if (!$optionId || $existingOption->getParentId() != $parentId) { + //If option ID is empty or existing option's parent ID is different + //we'd need a new ID for the option. + $option->setOptionId(null); + $option->setDefaultTitle($option->getTitle()); + if (is_array($option->getProductLinks())) { + $linksToAdd = $option->getProductLinks(); + } + } else { + if (!$existingOption->getOptionId()) { + throw new NoSuchEntityException( + __("The option that was requested doesn't exist. Verify the entity and try again.") + ); + } + + $option->setData(array_merge($existingOption->getData(), $option->getData())); + $this->updateOptionSelection($bundleProduct, $option); + } + + try { + $this->optionResource->save($option); + } catch (\Exception $e) { + throw new CouldNotSaveException(__("The option couldn't be saved."), $e); + } + + /** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */ + foreach ($linksToAdd as $linkedProduct) { + $this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct); + } + + return $option; + } + + /** + * Update option selections + * + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param \Magento\Bundle\Api\Data\OptionInterface $option + * @return void + */ + private function updateOptionSelection(ProductInterface $product, OptionInterface $option) + { + $optionId = $option->getOptionId(); + $existingLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); + $linksToAdd = []; + $linksToUpdate = []; + $linksToDelete = []; + if (is_array($option->getProductLinks())) { + $productLinks = $option->getProductLinks(); + foreach ($productLinks as $productLink) { + if (!$productLink->getId() && !$productLink->getSelectionId()) { + $linksToAdd[] = $productLink; + } else { + $linksToUpdate[] = $productLink; + } + } + /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */ + $linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate); + } + foreach ($linksToUpdate as $linkedProduct) { + $this->linkManagement->saveChild($product->getSku(), $linkedProduct); + } + foreach ($linksToDelete as $linkedProduct) { + $this->linkManagement->removeChild( + $product->getSku(), + $option->getOptionId(), + $linkedProduct->getSku() + ); + } + foreach ($linksToAdd as $linkedProduct) { + $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); + } + } +} diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php index b5e5244a11fda..36f7c44e3ea09 100644 --- a/app/code/Magento/Bundle/Model/OptionRepository.php +++ b/app/code/Magento/Bundle/Model/OptionRepository.php @@ -7,14 +7,14 @@ namespace Magento\Bundle\Model; +use Magento\Bundle\Model\Option\SaveAction; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; /** + * Repository for performing CRUD operations for a bundle product's options. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInterface @@ -39,11 +39,6 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt */ protected $optionResource; - /** - * @var \Magento\Store\Model\StoreManager - */ - protected $storeManager; - /** * @var \Magento\Bundle\Api\ProductLinkManagementInterface */ @@ -54,53 +49,44 @@ class OptionRepository implements \Magento\Bundle\Api\ProductOptionRepositoryInt */ protected $productOptionList; - /** - * @var Product\LinksList - */ - protected $linkList; - /** * @var \Magento\Framework\Api\DataObjectHelper */ protected $dataObjectHelper; /** - * @var \Magento\Framework\EntityManager\MetadataPool + * @var SaveAction */ - private $metadataPool; + private $optionSave; /** * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param Product\Type $type * @param \Magento\Bundle\Api\Data\OptionInterfaceFactory $optionFactory * @param \Magento\Bundle\Model\ResourceModel\Option $optionResource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Bundle\Api\ProductLinkManagementInterface $linkManagement * @param Product\OptionList $productOptionList - * @param Product\LinksList $linkList * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper - * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @param SaveAction $optionSave */ public function __construct( \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, \Magento\Bundle\Model\Product\Type $type, \Magento\Bundle\Api\Data\OptionInterfaceFactory $optionFactory, \Magento\Bundle\Model\ResourceModel\Option $optionResource, - \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Bundle\Api\ProductLinkManagementInterface $linkManagement, \Magento\Bundle\Model\Product\OptionList $productOptionList, - \Magento\Bundle\Model\Product\LinksList $linkList, - \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + SaveAction $optionSave ) { $this->productRepository = $productRepository; $this->type = $type; $this->optionFactory = $optionFactory; $this->optionResource = $optionResource; - $this->storeManager = $storeManager; $this->linkManagement = $linkManagement; $this->productOptionList = $productOptionList; - $this->linkList = $linkList; $this->dataObjectHelper = $dataObjectHelper; + $this->optionSave = $optionSave; } /** @@ -118,7 +104,7 @@ public function get($sku, $optionId) ); } - $productLinks = $this->linkList->getItems($product, $optionId); + $productLinks = $this->linkManagement->getChildren($product->getSku(), $optionId); /** @var \Magento\Bundle\Api\Data\OptionInterface $option */ $optionDataObject = $this->optionFactory->create(); @@ -177,7 +163,9 @@ public function deleteById($sku, $optionId) $product = $this->getProduct($sku); $optionCollection = $this->type->getOptionsCollection($product); $optionCollection->setIdFilter($optionId); - return $this->delete($optionCollection->getFirstItem()); + $hasBeenDeleted = $this->delete($optionCollection->getFirstItem()); + $this->productRepository->save($product); + return $hasBeenDeleted; } /** @@ -187,51 +175,13 @@ public function save( \Magento\Catalog\Api\Data\ProductInterface $product, \Magento\Bundle\Api\Data\OptionInterface $option ) { - $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - - $option->setStoreId($product->getStoreId()); - $parentId = $product->getData($metadata->getLinkField()); - $option->setParentId($parentId); - - $optionId = $option->getOptionId(); - $linksToAdd = []; - $optionCollection = $this->type->getOptionsCollection($product); - $optionCollection->setIdFilter($option->getOptionId()); - $optionCollection->setProductLinkFilter($parentId); - - /** @var \Magento\Bundle\Model\Option $existingOption */ - $existingOption = $optionCollection->getFirstItem(); - if (!$optionId || $existingOption->getParentId() != $parentId) { - //If option ID is empty or existing option's parent ID is different - //we'd need a new ID for the option. - $option->setOptionId(null); - $option->setDefaultTitle($option->getTitle()); - if (is_array($option->getProductLinks())) { - $linksToAdd = $option->getProductLinks(); - } - } else { - if (!$existingOption->getOptionId()) { - throw new NoSuchEntityException( - __("The option that was requested doesn't exist. Verify the entity and try again.") - ); - } + $savedOption = $this->optionSave->save($product, $option); - $option->setData(array_merge($existingOption->getData(), $option->getData())); - $this->updateOptionSelection($product, $option); - } + $product->setIsRelationsChanged(true); - try { - $this->optionResource->save($option); - } catch (\Exception $e) { - throw new CouldNotSaveException(__("The option couldn't be saved."), $e); - } + $this->productRepository->save($product); - /** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */ - foreach ($linksToAdd as $linkedProduct) { - $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); - } - $product->setIsRelationsChanged(true); - return $option->getOptionId(); + return $savedOption->getOptionId(); } /** @@ -325,16 +275,4 @@ private function compareLinks(array $firstArray, array $secondArray) return $result; } - - /** - * Get MetadataPool instance - * @return MetadataPool - */ - private function getMetadataPool() - { - if (!$this->metadataPool) { - $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class); - } - return $this->metadataPool; - } } diff --git a/app/code/Magento/Bundle/Model/Product/SaveHandler.php b/app/code/Magento/Bundle/Model/Product/SaveHandler.php index 8517cec6aff6d..e5fa688c7fece 100644 --- a/app/code/Magento/Bundle/Model/Product/SaveHandler.php +++ b/app/code/Magento/Bundle/Model/Product/SaveHandler.php @@ -6,6 +6,7 @@ namespace Magento\Bundle\Model\Product; use Magento\Bundle\Api\Data\OptionInterface; +use Magento\Bundle\Model\Option\SaveAction; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository; use Magento\Bundle\Api\ProductLinkManagementInterface; @@ -33,19 +34,26 @@ class SaveHandler implements ExtensionInterface */ private $metadataPool; + /** + * @var SaveAction + */ + private $optionSave; + /** * @param OptionRepository $optionRepository * @param ProductLinkManagementInterface $productLinkManagement + * @param SaveAction $optionSave * @param MetadataPool|null $metadataPool */ public function __construct( OptionRepository $optionRepository, ProductLinkManagementInterface $productLinkManagement, + SaveAction $optionSave, MetadataPool $metadataPool = null ) { $this->optionRepository = $optionRepository; $this->productLinkManagement = $productLinkManagement; - + $this->optionSave = $optionSave; $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); } @@ -103,7 +111,7 @@ public function execute($entity, $arguments = []) } //Saving active options. foreach ($options as $option) { - $this->optionRepository->save($entity, $option); + $this->optionSave->save($entity, $option); } $entity->setCopyFromView(false); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php new file mode 100644 index 0000000000000..d9f1095000c0a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/OptionRepositoryTest.php @@ -0,0 +1,78 @@ +objectManager = Bootstrap::getObjectManager(); + + $this->optionRepository = $this->objectManager->get(OptionRepository::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoDataFixture Magento/Bundle/_files/empty_bundle_product.php + */ + public function testBundleProductIsSaleableAfterNewOptionSave() + { + $bundleProduct = $this->productRepository->get('bundle-product'); + + /** @var OptionInterface $newOption */ + $newOption = $this->objectManager->create(OptionInterfaceFactory::class)->create(); + /** @var LinkInterface $productLink */ + $productLink = $this->objectManager->create(LinkInterfaceFactory::class)->create(); + + $newOption->setTitle('new-option'); + $newOption->setRequired(true); + $newOption->setType('select'); + $newOption->setSku($bundleProduct->getSku()); + + $productLink->setSku('simple'); + $productLink->setQty(1); + $productLink->setIsDefault(true); + $productLink->setCanChangeQuantity(0); + + $newOption->setProductLinks([$productLink]); + + $optionId = $this->optionRepository->save($bundleProduct, $newOption); + $bundleProduct = $this->productRepository->get($bundleProduct->getSku(), false, null, true); + + $this->assertNotNull($optionId, 'Bundle option was not saved correctly'); + $this->assertTrue($bundleProduct->isSaleable(), 'Bundle product should show as in stock once option is added'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php new file mode 100644 index 0000000000000..cc20b293e69d1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product.php @@ -0,0 +1,29 @@ +create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId('bundle') + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setPriceView(0) + ->setPriceType(0) + ->setShipmentType(1) + ->setWeightType(0) + ->setDescription('description') + ->setPrice(99); + +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php new file mode 100644 index 0000000000000..a124661a45bac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/empty_bundle_product_rollback.php @@ -0,0 +1,23 @@ +get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('bundle-product', false, null, true); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php index 09aca0fe9f454..864d2dfafe53b 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php @@ -3,22 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + /* * Since the bundle product creation GUI doesn't allow to choose values for bundled products' custom options, * bundled items should not contain products with required custom options. * However, if to create such a bundle product, it will be always out of stock. */ require __DIR__ . '/../../../Magento/Catalog/_files/products.php'; + /** @var $objectManager \Magento\TestFramework\ObjectManager */ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $sampleProduct = $productRepository->get('simple'); + /** @var $product \Magento\Catalog\Model\Product */ $product = $objectManager->create(\Magento\Catalog\Model\Product::class); $product->setTypeId('bundle') ->setId(3) ->setAttributeSetId(4) + ->setWeight(2) ->setWebsiteIds([1]) ->setName('Bundle Product') ->setSku('bundle-product') @@ -26,8 +30,10 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setPriceView(1) + ->setSkuType(1) + ->setWeightType(1) ->setPriceType(1) - ->setShipmentType(1) + ->setShipmentType(0) ->setPrice(10.0) ->setBundleOptionsData( [ @@ -44,13 +50,16 @@ [ [ 'product_id' => $sampleProduct->getId(), + 'selection_price_value' => 2.75, 'selection_qty' => 1, 'selection_can_change_qty' => 1, 'delete' => '', + ], ], ] ); + if ($product->getBundleOptionsData()) { $options = []; foreach ($product->getBundleOptionsData() as $key => $optionData) { @@ -59,6 +68,7 @@ ->create(['data' => $optionData]); $option->setSku($product->getSku()); $option->setOptionId(null); + $links = []; $bundleLinks = $product->getBundleSelectionsData(); if (!empty($bundleLinks[$key])) { @@ -70,6 +80,7 @@ $linkProduct = $productRepository->getById($linkData['product_id']); $link->setSku($linkProduct->getSku()); $link->setQty($linkData['selection_qty']); + $link->setPrice($linkData['selection_price_value']); if (isset($linkData['selection_can_change_qty'])) { $link->setCanChangeQuantity($linkData['selection_can_change_qty']); } @@ -85,4 +96,5 @@ $extension->setBundleProductOptions($options); $product->setExtensionAttributes($extension); } -$product->save(); + +$productRepository->save($product, true); diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 5dc1199c0e9da..a2927d51a3585 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -39887,7 +39887,7 @@ if (totalCount == null) { - + true From ba0143b103c3cf56483e7303fbdd0815999ae077 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Fri, 4 May 2018 13:17:13 -0500 Subject: [PATCH 106/236] MAGETWO-90177: Image broken on storefront with secure key enabled Fix regex matching if directive url contains ?SID=... query string --- .../Tinymce3/view/base/web/tinymce3Adapter.js | 12 +- .../tests/lib/mage/wysiwygAdapter.test.js | 103 +++++++++++++----- .../wysiwyg/tiny_mce/tinymce4Adapter.js | 12 +- 3 files changed, 92 insertions(+), 35 deletions(-) diff --git a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js index 8989cc7e2d6ee..474861a523878 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js +++ b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js @@ -539,9 +539,15 @@ define([ * @return {*} */ decodeDirectives: function (content) { - // escape special chars in directives url to use it in regular expression - var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*') + '/?'); + var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive + // escape special chars in directives url to use in regular expression + regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), + regexDirectiveUrl = regexEscapedDirectiveUrl + .replace( + '%directive%', + '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*' + ) + '/?(\\\\?[^"]*)?', // allow optional query string + reg = new RegExp(regexDirectiveUrl); return content.gsub(reg, function (match) { return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); diff --git a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js index 4a972d68e6ba5..700d19d6be678 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js @@ -27,7 +27,7 @@ define([ describe('wysiwygAdapter', function () { describe('encoding and decoding directives', function () { function runTests(decodedHtml, encodedHtml) { - var encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace('src="([^"]+)', 'src="$1/"'); + var encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace(/src="((?:(?!"|\\\?).)*)/, 'src="$1/'); describe('"encodeDirectives" method', function () { it('converts media directive img src to directive URL', function () { @@ -47,48 +47,93 @@ define([ it('converts directive URL img src with a trailing forward slash ' + 'to media url without a trailing forward slash', function () { + expect(encodedHtmlWithForwardSlashInImgSrc).not.toEqual(encodedHtml); expect(obj.decodeDirectives(encodedHtmlWithForwardSlashInImgSrc)).toEqual(decodedHtml); } ); }); } - describe('without secret key', function () { - var decodedHtml = '

' + - '

', - encodedHtml = '

' + - '' + - '

'; - - beforeEach(function () { - obj.initialize('id', { - 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' + describe('without SID in directive query string', function () { + describe('without secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': 'http://example.com/admin/cms/wysiwyg/directive/' + }); }); + + runTests(decodedHtml, encodedHtml); }); - runTests(decodedHtml, encodedHtml); + describe('with secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + + '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58/'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); + }); }); - describe('with secret key', function () { - var decodedHtml = '

' + - '

', - encodedHtml = '

' + - '' + - '

', - directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + - '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58/'; - - beforeEach(function () { - obj.initialize('id', { - 'directives_url': directiveUrl + describe('with SID in directive query string', function () { + describe('without secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive?SID=something'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); }); + + runTests(decodedHtml, encodedHtml); }); - runTests(decodedHtml, encodedHtml); + describe('with secret key', function () { + var decodedHtml = '

' + + '

', + encodedHtml = '

' + + '' + + '

', + directiveUrl = 'http://example.com/admin/cms/wysiwyg/directive/key/' + + '5552655d13a141099d27f5d5b0c58869423fd265687167da12cad2bb39aa9a58?SID=something'; + + beforeEach(function () { + obj.initialize('id', { + 'directives_url': directiveUrl + }); + }); + + runTests(decodedHtml, encodedHtml); + }); }); }); }); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index eba407645c42b..8f20243383db8 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -608,9 +608,15 @@ define([ * @return {*} */ decodeDirectives: function (content) { - // escape special chars in directives url to use it in regular expression - var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*') + '/?'); + var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive + // escape special chars in directives url to use in regular expression + regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), + regexDirectiveUrl = regexEscapedDirectiveUrl + .replace( + '%directive%', + '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*' + ) + '/?(\\\\?[^"]*)?', // allow optional query string + reg = new RegExp(regexDirectiveUrl); return content.gsub(reg, function (match) { return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '"'); From b0669f3e31bdc7e254e6bb344bb323948d8fd752 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Fri, 4 May 2018 13:47:03 -0500 Subject: [PATCH 107/236] MAGETWO-90182: Alter option saving mechanism - fix save action --- .../Bundle/Model/Option/SaveAction.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php index cf893820b5179..895825a8b0bd7 100644 --- a/app/code/Magento/Bundle/Model/Option/SaveAction.php +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -156,4 +156,38 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct); } } + + /** + * Computes the difference between given arrays. + * + * @param \Magento\Bundle\Api\Data\LinkInterface[] $firstArray + * @param \Magento\Bundle\Api\Data\LinkInterface[] $secondArray + * + * @return array + */ + private function compareLinks(array $firstArray, array $secondArray) + { + $result = []; + + $firstArrayIds = []; + $firstArrayMap = []; + + $secondArrayIds = []; + + foreach ($firstArray as $item) { + $firstArrayIds[] = $item->getId(); + + $firstArrayMap[$item->getId()] = $item; + } + + foreach ($secondArray as $item) { + $secondArrayIds[] = $item->getId(); + } + + foreach (array_diff($firstArrayIds, $secondArrayIds) as $id) { + $result[] = $firstArrayMap[$id]; + } + + return $result; + } } From 1c912d87e19f5bfd55808ea0c37c139ad0eafa49 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Fri, 4 May 2018 15:37:19 -0500 Subject: [PATCH 108/236] MAGETWO-90182: Alter option saving mechanism - Set relations to true --- app/code/Magento/Bundle/Model/Option/SaveAction.php | 2 ++ app/code/Magento/Bundle/Model/OptionRepository.php | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php index 895825a8b0bd7..1d809ba4b8e8c 100644 --- a/app/code/Magento/Bundle/Model/Option/SaveAction.php +++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php @@ -113,6 +113,8 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option) $this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct); } + $bundleProduct->setIsRelationsChanged(true); + return $option; } diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php index 36f7c44e3ea09..a15d2dcf6e049 100644 --- a/app/code/Magento/Bundle/Model/OptionRepository.php +++ b/app/code/Magento/Bundle/Model/OptionRepository.php @@ -177,8 +177,6 @@ public function save( ) { $savedOption = $this->optionSave->save($product, $option); - $product->setIsRelationsChanged(true); - $this->productRepository->save($product); return $savedOption->getOptionId(); From 435b165e37048955fdefd4c4d9537ac1dc2d260e Mon Sep 17 00:00:00 2001 From: gwharton Date: Sat, 5 May 2018 23:12:05 +0100 Subject: [PATCH 109/236] [2.3-develop] [ForwardPort] Port of #12285 The option false for mobile device don't work in product view page gallery Change the type of config variables of values "true" and "false" to type boolean, instead of string. --- .../Framework/Config/ConverterTest.php | 92 +++++++++++++++++++ .../Magento/Framework/Config/Converter.php | 4 +- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php new file mode 100644 index 0000000000000..f68e36d39a3bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -0,0 +1,92 @@ +loadXML($sourceString); + $actual = $this->converter->convert($document); + + self::assertEquals( + $expected, + $actual + ); + } + + /** + * Data provider for testParseVarElement. + * + * @return array + */ + public function parseVarElementDataProvider() + { + $sourceString = <<<'XML' + + + + some string + 1 + 0 + true + false + + +XML; + $expectedResult = [ + 'vars' => [ + 'Magento_Test' => [ + 'str' => 'some string', + 'int-1' => '1', + 'int-0' => '0', + 'bool-true' => true, + 'bool-false' => false + ] + ] + ]; + + return [ + [ + $sourceString, + $expectedResult + ], + ]; + } + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->converter = $this->objectManager->get(Converter::class); + } +} diff --git a/lib/internal/Magento/Framework/Config/Converter.php b/lib/internal/Magento/Framework/Config/Converter.php index 0401471f27ea5..b15082b28b2f8 100644 --- a/lib/internal/Magento/Framework/Config/Converter.php +++ b/lib/internal/Magento/Framework/Config/Converter.php @@ -103,7 +103,9 @@ protected function parseVarElement(\DOMElement $node) } } if (!count($result)) { - $result = $node->nodeValue; + $result = (strtolower($node->nodeValue) !== 'true' && strtolower($node->nodeValue) !== 'false') + ? $node->nodeValue + : filter_var($node->nodeValue, FILTER_VALIDATE_BOOLEAN); } return $result; } From 163d4cc63d19c3a3bd94697dd02c0d96696df799 Mon Sep 17 00:00:00 2001 From: gwharton Date: Sat, 5 May 2018 23:20:13 +0100 Subject: [PATCH 110/236] [2.3-develop] [ForwardPort] Port of #15020 Update Gallery Template to handle boolean config Variables Due to changes implemented in the resolution to #12285, boolean configuration variables are now properly typed booleans, instead of the strings "true" and "false". Without this fix applied, config vals that were true were being output in the gallery template javascript as : 1 and values that were false were being output as : This causes javascript errors for any item that is set to false. --- .../templates/product/view/gallery.phtml | 40 +++++-------------- 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml index 5a064b33355a4..1bfa30478df8a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml @@ -47,21 +47,11 @@ "data": getGalleryImagesJson() ?>, "options": { "nav": "getVar("gallery/nav") ?>", - getVar("gallery/loop"))): ?> - "loop": getVar("gallery/loop") ?>, - - getVar("gallery/keyboard"))): ?> - "keyboard": getVar("gallery/keyboard") ?>, - - getVar("gallery/arrows"))): ?> - "arrows": getVar("gallery/arrows") ?>, - - getVar("gallery/allowfullscreen"))): ?> - "allowfullscreen": getVar("gallery/allowfullscreen") ?>, - - getVar("gallery/caption"))): ?> - "showCaption": getVar("gallery/caption") ?>, - + "loop": getVar("gallery/loop") ? 'true' : 'false' ?>, + "keyboard": getVar("gallery/keyboard") ? 'true' : 'false' ?>, + "arrows": getVar("gallery/arrows") ? 'true' : 'false' ?>, + "allowfullscreen": getVar("gallery/allowfullscreen") ? 'true' : 'false' ?>, + "showCaption": getVar("gallery/caption") ? 'true' : 'false' ?>, "width": "getImageAttribute('product_page_image_medium', 'width') ?>", "thumbwidth": "getImageAttribute('product_page_image_small', 'width') ?>", getImageAttribute('product_page_image_small', 'height') || $block->getImageAttribute('product_page_image_small', 'width')): ?> @@ -76,28 +66,18 @@ "transitionduration": getVar("gallery/transition/duration") ?>, "transition": "getVar("gallery/transition/effect") ?>", - getVar("gallery/navarrows"))): ?> - "navarrows": getVar("gallery/navarrows") ?>, - + "navarrows": getVar("gallery/navarrows") ? 'true' : 'false' ?>, "navtype": "getVar("gallery/navtype") ?>", "navdir": "getVar("gallery/navdir") ?>" }, "fullscreen": { "nav": "getVar("gallery/fullscreen/nav") ?>", - getVar("gallery/fullscreen/loop")): ?> - "loop": getVar("gallery/fullscreen/loop") ?>, - + "loop": getVar("gallery/fullscreen/loop") ? 'true' : 'false' ?>, "navdir": "getVar("gallery/fullscreen/navdir") ?>", - getVar("gallery/transition/navarrows")): ?> - "navarrows": getVar("gallery/fullscreen/navarrows") ?>, - + "navarrows": getVar("gallery/fullscreen/navarrows") ? 'true' : 'false' ?>, "navtype": "getVar("gallery/fullscreen/navtype") ?>", - getVar("gallery/fullscreen/arrows")): ?> - "arrows": getVar("gallery/fullscreen/arrows") ?>, - - getVar("gallery/fullscreen/caption")): ?> - "showCaption": getVar("gallery/fullscreen/caption") ?>, - + "arrows": getVar("gallery/fullscreen/arrows") ? 'true' : 'false' ?>, + "showCaption": getVar("gallery/fullscreen/caption") ? 'true' : 'false' ?>, getVar("gallery/fullscreen/transition/duration")): ?> "transitionduration": getVar("gallery/fullscreen/transition/duration") ?>, From 0a4d090ca7d095ec61af469780bd440d144e25a4 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Sun, 6 May 2018 07:56:36 +0300 Subject: [PATCH 111/236] ENGCOM-1103: [Forwardport] Category\Collection::joinUrlRewrite should use the store set on the collection #14381 --- .../Catalog/Model/ResourceModel/Category/CollectionTest.php | 2 ++ .../Model/ResourceModel/_files/category_multiple_stores.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php index 7e26cdb921f39..3b1c23e2ae10c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Category/CollectionTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\ResourceModel\Category; class CollectionTest extends \PHPUnit\Framework\TestCase diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php index 90bf630000e72..84b57f8706620 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/category_multiple_stores.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + /** @var \Magento\Catalog\Model\CategoryFactory $factory */ $factory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\CategoryFactory::class From 4113f26f34e1c2e7035b0f294999aa8add6e6c52 Mon Sep 17 00:00:00 2001 From: gwharton Date: Sun, 6 May 2018 11:00:08 +0100 Subject: [PATCH 112/236] Fixed Travis Build Problems Fixed Travis Build Problems --- .../testsuite/Magento/Framework/Config/ConverterTest.php | 3 ++- lib/internal/Magento/Framework/Config/Converter.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php index f68e36d39a3bf..c94c081898607 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -51,7 +51,8 @@ public function parseVarElementDataProvider() { $sourceString = <<<'XML' - + some string 1 diff --git a/lib/internal/Magento/Framework/Config/Converter.php b/lib/internal/Magento/Framework/Config/Converter.php index b15082b28b2f8..3e66f641b8697 100644 --- a/lib/internal/Magento/Framework/Config/Converter.php +++ b/lib/internal/Magento/Framework/Config/Converter.php @@ -103,9 +103,9 @@ protected function parseVarElement(\DOMElement $node) } } if (!count($result)) { - $result = (strtolower($node->nodeValue) !== 'true' && strtolower($node->nodeValue) !== 'false') + $result = (strtolower($node->nodeValue) !== 'true' && strtolower($node->nodeValue) !== 'false') ? $node->nodeValue - : filter_var($node->nodeValue, FILTER_VALIDATE_BOOLEAN); + : filter_var($node->nodeValue, FILTER_VALIDATE_BOOLEAN); } return $result; } From c0b9e8f5ed1accb05cf2e1f1effa401ae1946d4c Mon Sep 17 00:00:00 2001 From: gwharton Date: Sun, 6 May 2018 11:35:11 +0100 Subject: [PATCH 113/236] Added strict types declaration --- .../testsuite/Magento/Framework/Config/ConverterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php index c94c081898607..a68686b755c35 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -1,4 +1,4 @@ - Date: Sun, 6 May 2018 20:47:11 +0100 Subject: [PATCH 114/236] Attempt to fix codacy issue --- .../Magento/Framework/Config/ConverterTest.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php index a68686b755c35..08cfa2efb9b2f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -6,18 +6,11 @@ namespace Magento\Framework\Config; -use Magento\Framework\ObjectManagerInterface; - /** * Tests Magento\Framework\Config\Convert */ class ConverterTest extends \PHPUnit\Framework\TestCase { - /** - * @var ObjectManagerInterface - */ - private $objectManager; - /** * @var Converter */ @@ -87,7 +80,6 @@ public function parseVarElementDataProvider() */ protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->converter = $this->objectManager->get(Converter::class); + $this->converter = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Framework\Config\Converter::class); } } From 2701a4f7223694f8c7d7a2adb4abb62417c2b5a5 Mon Sep 17 00:00:00 2001 From: gwharton Date: Sun, 6 May 2018 21:58:17 +0100 Subject: [PATCH 115/236] Fix Travis Long Line Check --- .../testsuite/Magento/Framework/Config/ConverterTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php index 08cfa2efb9b2f..5b39a2afb9d6c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Config/ConverterTest.php @@ -80,6 +80,7 @@ public function parseVarElementDataProvider() */ protected function setUp() { - $this->converter = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Framework\Config\Converter::class); + $this->converter = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Framework\Config\Converter::class); } } From a477ee278de61a15209163e2bba2a6c3a619453c Mon Sep 17 00:00:00 2001 From: Stas Kozar Date: Mon, 7 May 2018 11:36:19 +0300 Subject: [PATCH 116/236] MAGETWO-90292: 8410: Custom Checkout Step and Shipping Step are Highlighted and Combined upon Checkout page load #975 --- .../view/frontend/web/js/model/step-navigator.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js index 3827a174b3396..c707792111c82 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js @@ -66,7 +66,7 @@ define([ * @param {*} sortOrder */ registerStep: function (code, alias, title, isVisible, navigate, sortOrder) { - var hash; + var hash, active; if ($.inArray(code, this.validCodes) !== -1) { throw new DOMException('Step code [' + code + '] already registered in step navigator'); @@ -87,6 +87,12 @@ define([ navigate: navigate, sortOrder: sortOrder }); + active = this.getActiveItemIndex(); + steps.each(function (elem, index) { + if (active !== index) { + elem.isVisible(false); + } + }); this.stepCodes.push(code); hash = window.location.hash.replace('#', ''); @@ -111,10 +117,14 @@ define([ getActiveItemIndex: function () { var activeIndex = 0; - steps().sort(this.sortItems).forEach(function (element, index) { + steps().sort(this.sortItems).some(function (element, index) { if (element.isVisible()) { activeIndex = index; + + return true; } + + return false; }); return activeIndex; From 6a11839441eebccb76376017fb35e148299d5db1 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Mon, 7 May 2018 12:28:02 +0300 Subject: [PATCH 117/236] MAGETWO-90285: 8830: Can`t delete row in dynamicRows component #921 --- .../Product/Form/Modifier/Attributes.php | 5 ++++ .../product_attribute_add_form.xml | 5 ---- .../adminhtml/web/js/form/element/input.js | 11 ++++++-- .../template/form/element/action-delete.html | 2 +- .../product_attribute_add_form.xml | 24 +++-------------- .../web/js/form/element/swatch-visual.js | 26 ++++++++++++++++--- .../adminhtml/web/template/swatch-visual.html | 2 +- .../base/web/js/dynamic-rows/dynamic-rows.js | 8 ++++++ 8 files changed, 49 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index aec6549f400fc..683a96133ad30 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -182,6 +182,11 @@ private function customizeAddAttributeModal(array $meta) . '.create_new_attribute_modal', 'actionName' => 'toggleModal', ], + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal.product_attribute_add_form', + 'actionName' => 'destroyInserted' + ], [ 'targetName' => 'product_form.product_form.add_attribute_modal' diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml index 44e13da69fc3b..6c5d37a92ea4a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -152,7 +152,6 @@ true true container - attribute_options.position @@ -189,12 +188,8 @@ - - true - text false - position diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js index 51ffeaea0fc0c..2f6703cc92eac 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js @@ -54,9 +54,16 @@ define([ if (!_.isEmpty(this.suffixName) || _.isNumber(this.suffixName)) { suffixName = '.' + this.suffixName; } - this.dataScope = 'data.' + this.prefixName + '.' + this.elementName + suffixName; - this.links.value = this.provider + ':' + this.dataScope; + this.exportDataLink = 'data.' + this.prefixName + '.' + this.elementName + suffixName; + this.exports.value = this.provider + ':' + this.exportDataLink; + }, + + /** @inheritdoc */ + destroy: function () { + this._super(); + + this.source.remove(this.exportDataLink); }, /** diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html index d4cfb02611416..9a52dcefa3042 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html @@ -7,7 +7,7 @@
// // -// +// // // // @@ -361,7 +361,7 @@ footer.footer { // # Layout width // -// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applyed to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. +// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applied to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. // # Layout width variables // From 05372c74ead51d9df083cfb031d502e26cc9d058 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Tue, 8 May 2018 14:04:28 -0500 Subject: [PATCH 158/236] MAGETWO-90182: Fix integration tests --- .../Products/DataProvider/CategoryTree.php | 15 +++- .../ProductEntityAttributesForAst.php | 2 +- .../GraphQl/Bundle/BundleProductViewTest.php | 4 - .../Magento/GraphQl/Catalog/CategoryTest.php | 82 ++++++------------- .../GraphQl/Catalog/ProductSearchTest.php | 10 +-- .../GraphQl/Catalog/ProductViewTest.php | 1 - .../ConfigurableProductViewTest.php | 12 --- .../DownloadableProductViewTest.php | 2 - 8 files changed, 39 insertions(+), 89 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 615590fc3a36a..168aa28e85e5f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\FieldNode; use Magento\CatalogGraphQl\Model\Category\DepthCalculator; +use Magento\CatalogGraphQl\Model\Category\Hydrator; use Magento\CatalogGraphQl\Model\Category\LevelCalculator; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -52,25 +53,33 @@ class CategoryTree */ private $metadata; + /** + * @var Hydrator + */ + private $hydrator; + /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner * @param DepthCalculator $depthCalculator * @param LevelCalculator $levelCalculator * @param MetadataPool $metadata + * @param Hydrator $hydrator */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, DepthCalculator $depthCalculator, LevelCalculator $levelCalculator, - MetadataPool $metadata + MetadataPool $metadata, + Hydrator $hydrator ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; $this->depthCalculator = $depthCalculator; $this->levelCalculator = $levelCalculator; $this->metadata = $metadata; + $this->hydrator = $hydrator; } /** @@ -109,9 +118,11 @@ private function processTree(\Iterator $iterator) : array $category = $iterator->current(); $iterator->next(); $nextCategory = $iterator->current(); - $tree[$category->getId()] = $this->hydrateCategory($category); + $tree[$category->getId()] = $this->hydrator->hydrateCategory($category); if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { $tree[$category->getId()]['children'] = $this->processTree($iterator); + } else { + $tree[$category->getId()]['children'] = null; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index 99b892e868d2c..96bef3ffc09c4 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -33,7 +33,7 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface */ public function __construct( ConfigInterface $config, - array $additionalAttributes = ['min_price', 'max_price'] + array $additionalAttributes = ['min_price', 'max_price', 'category_ids'] ) { $this->config = $config; $this->additionalAttributes = $additionalAttributes; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php index 7d5143faf937a..8ccd9d0c94f61 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php @@ -40,7 +40,6 @@ public function testAllFieldsBundleProducts() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku dynamic_price @@ -126,7 +125,6 @@ public function testBundleProdutWithNotVisibleChildren() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku dynamic_price @@ -329,7 +327,6 @@ public function testAndMaxMinPriceBundleProduct() ... on PhysicalProductInterface { weight } - category_ids price { minimalPrice { amount { @@ -434,7 +431,6 @@ public function testNonExistentFieldQtyExceptionOnBundleProduct() ... on PhysicalProductInterface { weight } - category_ids ... on BundleProduct { dynamic_sku diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 440d475c80fa8..47b2448ad38e0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -34,29 +34,26 @@ protected function setUp() public function testCategoriesSubtree($categoryId, $expectedResponse) { $query = << 3, - 'expected_subtree' => [ - 'categories' => [ - 'category_tree' => [ - 'id' => 3, - 'level' => 2, - 'name' => 'Category 1', - 'path' => '1/2/3', - 'product_count' => 0, - 'children' => [ - [ - 'id' => 4, - 'name' => 'Category 1.1', - 'level' => 3, - 'is_active' => true, - 'path' => '1/2/3/4', - 'children' => [ - [ - 'id' => 5, - 'name' => 'Category 1.1.1', - 'level' => 4, - 'description' => null, - 'path' => '1/2/3/4/5', - ], - ], - ], - ], - ], - ], - ] - ], [ 'category_id' => 6, 'expected_subtree' => [ - 'categories' => [ - 'category_tree' => [ - 'id' => 6, - 'level' => 2, - 'name' => 'Category 2', - 'path' => '1/2/6', - 'product_count' => 0, - 'children' => [] - ], + 'category' => [ + 'id' => 6, + 'level' => 2, + 'name' => 'Category 2', + 'path' => '1/2/6', + 'product_count' => 0, + 'children' => [] ], ] ] @@ -181,6 +144,7 @@ public function testCategoriesTree() $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; $response = $this->graphQlQuery($query, [], '', $headerMap); + var_dump($response['category']['children']); $responseDataObject = new DataObject($response); //Some sort of smoke testing self::assertEquals( diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 19bd2f22bb396..b95e0f933ea04 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -634,8 +634,8 @@ public function testFilteringForProductInMultipleCategories() foreach ($categoryIds as $index => $value) { $categoryIds[$index] = [ 'id' => (int)$value]; } - $this->assertNotEmpty($response['products']['items'][0]['categories'], "Category_ids must not be empty"); - $this->assertNotNull($response['products']['items'][0]['categories'], "categoy_ids must not be null"); + $this->assertNotEmpty($response['products']['items'][0]['categories'], "Categories must not be empty"); + $this->assertNotNull($response['products']['items'][0]['categories'], "categories must not be null"); $this->assertEquals($categoryIds, $response['products']['items'][0]['categories']); /** @var MetadataPool $metaData */ $metaData = ObjectManager::getInstance()->get(MetadataPool::class); @@ -674,14 +674,12 @@ public function testFilterProductsByCategoryIds() sku name type_id - category_ids categories{ name id path children_count product_count - is_active } } total_count @@ -712,7 +710,6 @@ public function testFilterProductsByCategoryIds() foreach ($categoryIds as $index => $value) { $categoryIds[$index] = (int)$value; } - $this->assertEquals($response['products']['items'][$itemIndex]['category_ids'], $categoryIds); $categoryInResponse = array_map( null, $categoryIds, @@ -730,7 +727,6 @@ public function testFilterProductsByCategoryIds() 'path' => $category->getPath(), 'children_count' => $category->getChildrenCount(), 'product_count' => $category->getProductCount(), - 'is_active' => $category->getIsActive(), ] ); } @@ -1146,7 +1142,6 @@ public function testQueryWithNoSearchOrFilterArgumentException() ... on PhysicalProductInterface { weight } - category_ids } } } @@ -1180,7 +1175,6 @@ public function testFilterProductsThatAreOutOfStockWithConfigSettings() { sku name - category_ids } total_count diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 9a4101e5602af..a4c7f41df0e99 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -49,7 +49,6 @@ public function testQueryAllFieldsSimpleProduct() id categories { name - is_active url_path available_sort_by level diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php index 6ce4b522047ab..84c4e80d325ab 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php @@ -89,7 +89,6 @@ public function testQueryConfigurableProductLinks() } } } - category_ids ... on ConfigurableProduct { configurable_options { id @@ -110,7 +109,6 @@ public function testQueryConfigurableProductLinks() variants { product { id - category_ids name sku attribute_set_id @@ -305,10 +303,6 @@ private function assertConfigurableVariants($actualResponse) ); $indexValue = $variantArray['product']['sku']; unset($variantArray['product']['id']); - $this->assertTrue( - isset($variantArray['product']['category_ids']), - 'variant product doesn\'t contain category_ids key' - ); $this->assertTrue( isset($variantArray['product']['categories']), 'variant product doesn\'t contain categories key' @@ -327,12 +321,6 @@ private function assertConfigurableVariants($actualResponse) $this->assertEquals($actualValue, ['id' => $id]); unset($variantArray['product']['categories']); - $categoryIdsAttribute = $childProduct->getCustomAttribute('category_ids'); - $this->assertNotEmpty($categoryIdsAttribute, "Precondition failed: 'category_ids' must not be empty"); - $categoryIdsAttributeValue = $categoryIdsAttribute ? $categoryIdsAttribute->getValue() : []; - $this->assertEquals($categoryIdsAttributeValue, $variantArray['product']['category_ids']); - unset($variantArray['product']['category_ids']); - $mediaGalleryEntries = $childProduct->getMediaGalleryEntries(); $this->assertCount( 1, diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php index d8929bab8e9e5..57d39f04347bf 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php @@ -48,7 +48,6 @@ public function testQueryAllFieldsDownloadableProductsWithDownloadableFileAndSam } } } - category_ids ... on DownloadableProduct { links_title links_purchased_separately @@ -139,7 +138,6 @@ public function testDownloadableProductQueryWithNoSample() } } } - category_ids ... on DownloadableProduct { links_title links_purchased_separately From 29d5add300b84fef84810b764cc9427a80de963d Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Tue, 8 May 2018 14:19:11 -0500 Subject: [PATCH 159/236] MAGETWO-90182: Generate benchmark.jmx --- setup/performance-toolkit/benchmark.jmx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index f92584716c387..341edc7ab4654 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -39914,7 +39914,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39990,7 +39990,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40059,7 +40059,7 @@ if (totalCount == null) { false - {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40119,7 +40119,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40179,7 +40179,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -40239,7 +40239,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40308,7 +40308,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40395,7 +40395,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40462,7 +40462,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40720,7 +40720,7 @@ if (totalCount == null) { false - {"query":"{\n categories(filter: {root_category_id: 4}) {\n category_tree {\n id\n level\n is_active\n description\n all_children\n path\n path_in_store\n product_count\n url_key\n url_path\n children {\n id\n is_active\n description\n available_sort_by\n default_sort_by\n image\n level\n children {\n id\n is_active\n filter_price_range\n description\n image\n meta_keywords\n level\n is_anchor\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n categories(id: 4) {\n category {\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = From 252c3be0fdd178a053f531550f74f027f771a990 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Tue, 8 May 2018 14:33:06 -0500 Subject: [PATCH 160/236] MAGETWO-90182: remove issue causing static failure --- .../testsuite/Magento/GraphQl/Catalog/CategoryTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 47b2448ad38e0..b8c8a6aa3f988 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -144,7 +144,6 @@ public function testCategoriesTree() $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; $response = $this->graphQlQuery($query, [], '', $headerMap); - var_dump($response['category']['children']); $responseDataObject = new DataObject($response); //Some sort of smoke testing self::assertEquals( From 8e2dfd61876d46df70e019c18a4d29995ce73bec Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Tue, 8 May 2018 15:15:19 -0500 Subject: [PATCH 161/236] MAGETWO-90182: Remove nullifying children --- .../Model/Resolver/Products/DataProvider/CategoryTree.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 168aa28e85e5f..3c01579410638 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -121,8 +121,6 @@ private function processTree(\Iterator $iterator) : array $tree[$category->getId()] = $this->hydrator->hydrateCategory($category); if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { $tree[$category->getId()]['children'] = $this->processTree($iterator); - } else { - $tree[$category->getId()]['children'] = null; } } From 6c88c1010c9a1059a5d26c788326dce0b667b7b0 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Tue, 8 May 2018 15:52:27 -0500 Subject: [PATCH 162/236] MAGETWO-90182: Fix performance --- setup/performance-toolkit/benchmark.jmx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 341edc7ab4654..27a2b7d252386 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -39857,7 +39857,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39914,7 +39914,7 @@ adminUserList.add(vars.get("admin_user")); false - {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -39954,7 +39954,7 @@ adminUserList.add(vars.get("admin_user")); - String totalCount=vars.get("graphql_simple_products_query_total_count"); + zString totalCount=vars.get("graphql_simple_products_query_total_count"); if (totalCount == null) { Failure = true; @@ -39990,7 +39990,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40059,7 +40059,7 @@ if (totalCount == null) { false - {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40119,7 +40119,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40179,7 +40179,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -40239,7 +40239,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40308,7 +40308,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40395,7 +40395,7 @@ if (totalCount == null) { false - {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null} = @@ -40462,7 +40462,7 @@ if (totalCount == null) { false - {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n custom_layout\n custom_layout_update\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} + {"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null} = From fa227538f118e1cccc5404e39b2855d9fba7d733 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Tue, 8 May 2018 17:36:39 -0500 Subject: [PATCH 163/236] MAGETWO-90878: Build stabilization Tear down Squire injector after every spec --- .../js/view/payment/method-renderer/cc-form.test.js | 7 +++++++ .../js/view/payment/method-renderer/paypal.test.js | 7 +++++++ .../base/js/product/list/columns/final-price.test.js | 7 +++++++ .../Catalog/base/js/product/list/columns/image.test.js | 7 +++++++ .../Catalog/frontend/js/product/breadcrumbs.test.js | 7 +++++++ .../frontend/js/product/storage/data-storage.test.js | 7 +++++++ .../js/product/storage/ids-storage-compare.test.js | 7 +++++++ .../frontend/js/product/storage/ids-storage.test.js | 7 +++++++ .../frontend/js/product/storage/storage-service.test.js | 7 +++++++ .../Magento/Catalog/frontend/js/storage-manager.test.js | 7 +++++++ .../frontend/js/action/redirect-on-success.test.js | 7 +++++++ .../Magento/Checkout/frontend/js/model/cart/cache.test.js | 7 +++++++ .../js/model/cart/totals-processor/default.test.js | 7 +++++++ .../frontend/js/view/cart/shipping-estimation.test.js | 7 +++++++ .../Magento/Checkout/frontend/js/view/minicart.test.js | 7 +++++++ .../Checkout/frontend/js/view/summary/cart-items.test.js | 7 +++++++ .../frontend/js/model/agreement-validator.test.js | 5 +++++ .../frontend/js/model/place-order-mixin.test.js | 7 +++++++ .../js/model/set-payment-information-mixin.test.js | 7 +++++++ .../frontend/js/view/authentication-popup.test.js | 7 +++++++ .../frontend/web/js/view/instant-purchase.test.js | 5 +++++ .../frontend/js/in-context/express-checkout.test.js | 7 +++++++ .../method-renderer/in-context/checkout-express.test.js | 7 +++++++ .../method-renderer/paypal-express-abstract.test.js | 7 +++++++ .../web/js/view/checkout/summary/grand-total.test.js | 7 +++++++ .../view/frontend/web/js/view/add-home-breadcrumb.test.js | 5 +++++ .../Magento/Ui/base/js/form/element/multiselect.test.js | 7 +++++++ .../app/code/Magento/Ui/base/js/timeline/timeline.test.js | 7 +++++++ .../js/jasmine/tests/lib/mage/backend/bootstrap.test.js | 8 ++++++++ .../js/jasmine/tests/lib/mage/wysiwygAdapter.test.js | 4 ++-- 30 files changed, 200 insertions(+), 2 deletions(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js index 52739eec2782b..84db685c02987 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js @@ -56,6 +56,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('Check if payment code and message container are restored after onActiveChange call.', function () { var expectedMessageContainer = braintreeCcForm.messageContainer, expectedCode = braintreeCcForm.code; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js index a9987f5e01ba8..596017be9a33c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js @@ -64,6 +64,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('The PayPal::initAuthFlow throws an exception.', function () { spyOn(additionalValidator, 'validate').and.returnValue(true); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js index 1e3e066998d09..13b68aa4d9ea7 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/final-price.test.js @@ -28,6 +28,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/list/columns/final-price', function () { describe('"getPrice" method', function () { it('Check returned value', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js index 90ca32fcf8c23..776677ea04f11 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/base/js/product/list/columns/image.test.js @@ -37,6 +37,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/list/columns/image', function () { var image = { url: 'url', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js index 6ef6bb5c52769..b0516af46bc6b 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/breadcrumbs.test.js @@ -48,6 +48,13 @@ define([ ); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/breadcrumbs', function () { it('mixin is applied to Magento_Theme/js/view/breadcrumbs', function () { var breadcrumbMixins = defaultContext.config.config.mixins['Magento_Theme/js/view/breadcrumbs']; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js index 2ecfe41f29c13..fdd0b70997f35 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/data-storage.test.js @@ -44,6 +44,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/data-storage', function () { describe('"initCustomerDataInvalidateListener" method', function () { it('check returned value', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js index da162a2e9cf4f..aa68c498e0fea 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage-compare.test.js @@ -28,6 +28,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/ids-storage-compare', function () { describe('"providerDataHandler" method', function () { it('check calls "prepareData" and "add" method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js index e4d3bd62a862d..c394a2463f5e2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/ids-storage.test.js @@ -22,6 +22,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/ids-storage', function () { describe('"getDataFromLocalStorage" method', function () { it('check calls localStorage get method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js index 5530666c7ed70..4ef1aa2a98976 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js @@ -29,6 +29,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/product/storage/storage-service', function () { var config = { namespace: 'namespace', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js index 4514684edf116..571113f481bf3 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/storage-manager.test.js @@ -36,6 +36,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Catalog/js/storage-manager', function () { describe('"initStorages" method', function () { beforeEach(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js index 8961d99170dcd..4b053b4322dea 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/action/redirect-on-success.test.js @@ -27,6 +27,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('Checks if loader is called before redirect to success page.', function () { spyOn(window.location, 'replace').and.returnValue(false); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js index c796bd4f9f2ec..7f905a91048fa 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/cache.test.js @@ -30,6 +30,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/model/cart/cache', function () { describe('Check the "get" method', function () { it('Check default call with "cart-data" key', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js index 44f06279dcbef..47518bef19e56 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js @@ -89,6 +89,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/model/cart/totals-processor/default', function () { it('estimateTotals if data was cached', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js index 5e6dbfa5c0372..c1853d228c104 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/cart/shipping-estimation.test.js @@ -53,6 +53,13 @@ define(['squire', 'ko'], function (Squire, ko) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/cart/shipping-estimation', function () { describe('"initElement" method', function () { it('Check for return value and element that initiated.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js index 570de61052309..4f3949bd8124e 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/minicart.test.js @@ -41,6 +41,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/minicart', function () { describe('"getCartItems" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js index d1698b2fedac3..d64f07ab619ba 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/summary/cart-items.test.js @@ -68,6 +68,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Checkout/js/view/summary/cart-items', function () { describe('"getItemsQty" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js index afff4660cab41..ef33d49bf11d6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/agreement-validator.test.js @@ -36,6 +36,11 @@ define(['squire', 'jquery'], function (Squire, $) { afterEach(function () { $('.payment-method._active').remove(); + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); describe('Magento_CheckoutAgreements/js/model/agreement-validator', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js index 545daf0a330c9..1e3cf3e0280b6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js @@ -34,6 +34,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_CheckoutAgreements/js/model/place-order-mixin', function () { it('mixin is applied to Magento_Checkout/js/action/place-order', function () { var placeOrderMixins = defaultContext.config.config.mixins['Magento_Checkout/js/action/place-order']; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js index ed525bfd96a6c..8ea07b78b02c9 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js @@ -34,6 +34,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_CheckoutAgreements/js/model/set-payment-information-mixin', function () { it('mixin is applied to Magento_Checkout/js/action/set-payment-information', function () { var placeOrderMixins = defaultContext diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js index 3004d0266f5af..8a8216a820dd4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/view/authentication-popup.test.js @@ -44,6 +44,13 @@ define(['squire'], function (Squire) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Customer/js/view/authentication-popup', function () { describe('"isActive" method', function () { it('Check for return value.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js index 979e0dd351ee6..b61742b55fa2f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/InstantPurchase/frontend/web/js/view/instant-purchase.test.js @@ -41,6 +41,11 @@ define([ afterEach(function () { $('#product_addtocart_form').remove(); + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); it('Check initialized data.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js index 7a425e479b93b..0420256e20ef5 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js @@ -51,6 +51,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('clientConfig.click method', function () { it('Check for properties defined ', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js index 8b942818168cb..395dd96822ea5 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js @@ -58,6 +58,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('"click" method checks', function () { it('check success request', function () { mocks['Magento_Paypal/js/action/set-payment-method'].and.callFake(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index 47e3507ea1321..f05338ad1e7d2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -72,6 +72,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + it('showAcceptanceWindow is invoked when the anchor element of help link is clicked', function (done) { spyOn(paypalExpressAbstract, 'showAcceptanceWindow'); setTimeout(function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js index 4c78bd840d63e..2174c4a913ad0 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/grand-total.test.js @@ -35,6 +35,13 @@ define(['squire', 'ko'], function (Squire, ko) { }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Tax/js/view/checkout/summary/grand-total', function () { describe('"getGrandTotalExclTax" method', function () { it('Check if totals object empty.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js index 4f74905d1b3a3..9c7bbc161ac4a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Theme/view/frontend/web/js/view/add-home-breadcrumb.test.js @@ -37,6 +37,11 @@ define([ afterEach(function () { delete window.BASE_URL; + + try { + injector.clean(); + injector.remove(); + } catch (e) {} }); it('mixin is applied to Magento_Theme/js/view/breadcrumbs', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js index 165cc1aff3ac5..d443784efe050 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js @@ -45,6 +45,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Ui/js/form/element/multiselect', function () { describe('"setPrepareToSendData" method', function () { it('Check method call with empty array as parameter.', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js index 12354713400df..c82ca680336b4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/timeline/timeline.test.js @@ -35,6 +35,13 @@ define([ }); }); + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('Magento_Ui/js/timeline/timeline', function () { describe('isActive method', function () { diff --git a/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js b/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js index 834a6df75843f..396b62df0dfe9 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/backend/bootstrap.test.js @@ -19,6 +19,14 @@ define([ done(); }); }); + + afterEach(function () { + try { + injector.clean(); + injector.remove(); + } catch (e) {} + }); + describe('"sendPostponeRequest" method', function () { it('should insert "Error" notification if request failed', function (done) { jQuery('
').appendTo('body'); diff --git a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js index 09e7859066293..39fb5330a038a 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/wysiwygAdapter.test.js @@ -29,8 +29,8 @@ define([ /** * Tests encoding and decoding directives * - * @param {string} decodedHtml - * @param {string} encodedHtml + * @param {String} decodedHtml + * @param {String} encodedHtml */ function runTests(decodedHtml, encodedHtml) { var encodedHtmlWithForwardSlashInImgSrc = encodedHtml.replace(/src="((?:(?!"|\\\?).)*)/, 'src="$1/'); From 1e8ce468ec73a01be4dd7dee1c7a8c7bdffb5b92 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Wed, 9 May 2018 10:47:46 +0200 Subject: [PATCH 164/236] Fix reset password link with appropriate customer store --- .../Customer/Model/EmailNotification.php | 4 +- .../Test/Unit/Model/EmailNotificationTest.php | 112 +++++++++++++++++- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index 3e326e087dd5f..4d88d2dbda16b 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -314,9 +314,9 @@ private function getWebsiteStoreId($customer, $defaultStoreId = null) */ public function passwordReminder(CustomerInterface $customer) { - $storeId = $this->getWebsiteStoreId($customer); + $storeId = $customer->getStoreId(); if (!$storeId) { - $storeId = $this->storeManager->getStore()->getId(); + $storeId = $this->getWebsiteStoreId($customer);; } $customerEmailData = $this->getFullCustomerObject($customer); diff --git a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php index 61e58af78fcdf..d0967fe817204 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php @@ -312,6 +312,7 @@ public function sendNotificationEmailsDataProvider() public function testPasswordReminder() { $customerId = 1; + $customerWebsiteId = 1; $customerStoreId = 2; $customerEmail = 'email@email.com'; $customerData = ['key' => 'value']; @@ -320,6 +321,8 @@ public function testPasswordReminder() $sender = 'Sender'; $senderValues = ['name' => $sender, 'email' => $sender]; + $storeIds = [1, 2]; + $this->senderResolverMock ->expects($this->once()) ->method('resolve') @@ -328,6 +331,9 @@ public function testPasswordReminder() /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ $customer = $this->createMock(CustomerInterface::class); + $customer->expects($this->any()) + ->method('getWebsiteId') + ->willReturn($customerWebsiteId); $customer->expects($this->any()) ->method('getStoreId') ->willReturn($customerStoreId); @@ -346,10 +352,15 @@ public function testPasswordReminder() ->method('getStore') ->willReturn($this->storeMock); - $this->storeManagerMock->expects($this->at(1)) - ->method('getStore') - ->with($customerStoreId) - ->willReturn($this->storeMock); + $websiteMock = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getStoreIds']); + $websiteMock->expects($this->any()) + ->method('getStoreIds') + ->willReturn($storeIds); + + $this->storeManagerMock->expects($this->any()) + ->method('getWebsite') + ->with($customerWebsiteId) + ->willReturn($websiteMock); $this->customerRegistryMock->expects($this->once()) ->method('retrieveSecureData') @@ -396,6 +407,99 @@ public function testPasswordReminder() $this->model->passwordReminder($customer); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testPasswordReminderCustomerWithoutStoreId() + { + $customerId = 1; + $customerWebsiteId = 1; + $customerStoreId = null; + $customerEmail = 'email@email.com'; + $customerData = ['key' => 'value']; + $customerName = 'Customer Name'; + $templateIdentifier = 'Template Identifier'; + $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + $storeIds = [1, 2]; + $defaultStoreId = reset($storeIds); + $this->senderResolverMock + ->expects($this->once()) + ->method('resolve') + ->with($sender, $defaultStoreId) + ->willReturn($senderValues); + /** @var CustomerInterface | \PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->createMock(CustomerInterface::class); + $customer->expects($this->any()) + ->method('getWebsiteId') + ->willReturn($customerWebsiteId); + $customer->expects($this->any()) + ->method('getStoreId') + ->willReturn($customerStoreId); + $customer->expects($this->any()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->any()) + ->method('getEmail') + ->willReturn($customerEmail); + $this->storeMock->expects($this->any()) + ->method('getId') + ->willReturn($defaultStoreId); + $this->storeManagerMock->expects($this->at(0)) + ->method('getStore') + ->willReturn($this->storeMock); + $this->storeManagerMock->expects($this->at(1)) + ->method('getStore') + ->with($defaultStoreId) + ->willReturn($this->storeMock); + $websiteMock = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getStoreIds']); + $websiteMock->expects($this->any()) + ->method('getStoreIds') + ->willReturn($storeIds); + $this->storeManagerMock->expects($this->any()) + ->method('getWebsite') + ->with($customerWebsiteId) + ->willReturn($websiteMock); + + $this->customerRegistryMock->expects($this->once()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($this->customerSecureMock); + $this->dataProcessorMock->expects($this->once()) + ->method('buildOutputDataArray') + ->with($customer, CustomerInterface::class) + ->willReturn($customerData); + $this->customerViewHelperMock->expects($this->any()) + ->method('getCustomerName') + ->with($customer) + ->willReturn($customerName); + $this->customerSecureMock->expects($this->once()) + ->method('addData') + ->with($customerData) + ->willReturnSelf(); + $this->customerSecureMock->expects($this->once()) + ->method('setData') + ->with('name', $customerName) + ->willReturnSelf(); + $this->scopeConfigMock->expects($this->at(0)) + ->method('getValue') + ->with(EmailNotification::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, $defaultStoreId) + ->willReturn($templateIdentifier); + $this->scopeConfigMock->expects($this->at(1)) + ->method('getValue') + ->with(EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $defaultStoreId) + ->willReturn($sender); + $this->mockDefaultTransportBuilder( + $templateIdentifier, + $defaultStoreId, + $senderValues, + $customerEmail, + $customerName, + ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] + ); + $this->model->passwordReminder($customer); + } + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ From a267e2fcc088afbe1478c8033f8f8c8b9adbf89a Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Wed, 9 May 2018 10:51:02 +0200 Subject: [PATCH 165/236] Removed unnecessary empty line --- .../Magento/Customer/Test/Unit/Model/EmailNotificationTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php index d0967fe817204..318023d8068c5 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php @@ -320,7 +320,6 @@ public function testPasswordReminder() $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; $senderValues = ['name' => $sender, 'email' => $sender]; - $storeIds = [1, 2]; $this->senderResolverMock From be003df377aa7a15e999ed8b41e363eaf92de855 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 9 May 2018 11:02:41 -0500 Subject: [PATCH 166/236] MAGETWO-90182: Generate benchmark --- setup/performance-toolkit/benchmark.jmx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 27a2b7d252386..74144cd7a3a82 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -39954,7 +39954,7 @@ adminUserList.add(vars.get("admin_user")); - zString totalCount=vars.get("graphql_simple_products_query_total_count"); + String totalCount=vars.get("graphql_simple_products_query_total_count"); if (totalCount == null) { Failure = true; @@ -40179,7 +40179,7 @@ if (totalCount == null) { false - {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n category_ids\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n tax_class_id\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = From 472303cc48704c0bf19fe2835317f275724f3c9e Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Wed, 9 May 2018 11:57:44 -0500 Subject: [PATCH 167/236] MAGETWO-90375: Removing "Rush" theme work from 2.3.0 - removing rush theme because it will be delivered part of the PWA package --- app/design/frontend/Magento/rush/LICENSE.txt | 48 --- .../frontend/Magento/rush/LICENSE_AFL.txt | 48 --- .../rush/Magento_Theme/layout/default.xml | 11 - .../layout/default_head_blocks.xml | 19 -- .../Magento/rush/Magento_Theme/layouts.xml | 15 - .../Magento_Theme/page_layout/app_shell.xml | 11 - .../rush/Magento_Theme/templates/root.phtml | 23 -- .../frontend/Magento/rush/composer.json | 13 - app/design/frontend/Magento/rush/etc/view.xml | 311 ------------------ .../frontend/Magento/rush/media/preview.jpg | Bin 39314 -> 0 bytes .../frontend/Magento/rush/registration.php | 13 - app/design/frontend/Magento/rush/theme.xml | 13 - .../Magento/rush/web/css/_styles-l.less | 0 .../Magento/rush/web/css/_styles-m.less | 0 .../frontend/Magento/rush/web/images/logo.svg | 31 -- .../frontend/Magento/rush/web/js/foo.js | 1 - 16 files changed, 557 deletions(-) delete mode 100644 app/design/frontend/Magento/rush/LICENSE.txt delete mode 100644 app/design/frontend/Magento/rush/LICENSE_AFL.txt delete mode 100644 app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml delete mode 100644 app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml delete mode 100644 app/design/frontend/Magento/rush/Magento_Theme/layouts.xml delete mode 100644 app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml delete mode 100644 app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml delete mode 100644 app/design/frontend/Magento/rush/composer.json delete mode 100644 app/design/frontend/Magento/rush/etc/view.xml delete mode 100644 app/design/frontend/Magento/rush/media/preview.jpg delete mode 100644 app/design/frontend/Magento/rush/registration.php delete mode 100644 app/design/frontend/Magento/rush/theme.xml delete mode 100644 app/design/frontend/Magento/rush/web/css/_styles-l.less delete mode 100644 app/design/frontend/Magento/rush/web/css/_styles-m.less delete mode 100644 app/design/frontend/Magento/rush/web/images/logo.svg delete mode 100644 app/design/frontend/Magento/rush/web/js/foo.js diff --git a/app/design/frontend/Magento/rush/LICENSE.txt b/app/design/frontend/Magento/rush/LICENSE.txt deleted file mode 100644 index 36b2459f6aa63..0000000000000 --- a/app/design/frontend/Magento/rush/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -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 " 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/design/frontend/Magento/rush/LICENSE_AFL.txt b/app/design/frontend/Magento/rush/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/app/design/frontend/Magento/rush/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -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 " 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/design/frontend/Magento/rush/Magento_Theme/layout/default.xml b/app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml deleted file mode 100644 index 28e9d7e12f499..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layout/default.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml b/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml deleted file mode 100644 index 60fc5aa8f56ab..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layout/default_head_blocks.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml b/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml deleted file mode 100644 index 2ac2d369ade06..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/layouts.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml b/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml deleted file mode 100644 index a0fdf9bc9738e..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/page_layout/app_shell.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml b/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml deleted file mode 100644 index 5a2fb56828cc4..0000000000000 --- a/app/design/frontend/Magento/rush/Magento_Theme/templates/root.phtml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - -
- - diff --git a/app/design/frontend/Magento/rush/composer.json b/app/design/frontend/Magento/rush/composer.json deleted file mode 100644 index 99bf02d92a990..0000000000000 --- a/app/design/frontend/Magento/rush/composer.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "magento/theme-frontend-rush", - "description": "N/A", - "require": { - "php": "~7.1.3||~7.2.0", - "magento/framework": "*" - }, - "type": "magento2-theme", - "license": ["OSL-3.0", "AFL-3.0"], - "autoload": { - "files": ["registration.php"] - } -} diff --git a/app/design/frontend/Magento/rush/etc/view.xml b/app/design/frontend/Magento/rush/etc/view.xml deleted file mode 100644 index 1b62c30fa9c43..0000000000000 --- a/app/design/frontend/Magento/rush/etc/view.xml +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - 140 - 140 - - - - 152 - 188 - - - 110 - 160 - - - 240 - 300 - - - 240 - 300 - - - 100 - 100 - - - 285 - 285 - - - 113 - 113 - - - 75 - 75 - - - 100 - 100 - - - 78 - 78 - - - 240 - 300 - - - 270 - 270 - - - 78 - 78 - - - 265 - 265 - - - 140 - 140 - - - - 700 - 700 - - - 90 - 90 - - - 700 - 700 - - - 700 - 700 - - - - 240 - 300 - - - 88 - 110 - - - 90 - 90 - - - 76 - 76 - - - 135 - 135 - - - 75 - 75 - - - 240 - 300 - - - 75 - 90 - - - 76 - 76 - - - 270 - 207 - - - 240 - 300 - - - 75 - 90 - - - 76 - 76 - - - 270 - 270 - - - 140 - 140 - - - 285 - 285 - - - 75 - 75 - - - 75 - 75 - - - 135 - 135 - - - 75 - 90 - - - 140 - 140 - - - 75 - 90 - - - 113 - 113 - - - 240 - 300 - - - - - - - - thumbs - true - true - true - false - true - horizontal - true - slides - - slide - 500 - - - thumbs - true - false - false - horizontal - slides - - dissolve - 500 - - - - - - 20 - - - - - hover - false - - - - - - 767px - - - - dots - - - - - - - 100 - 275 - 48 - - 166 - 370 - - 0 - - - 58 - - - 1MB - - - Lib::jquery/jquery.min.js - Lib::jquery/jquery-ui-1.9.2.js - Lib::jquery/jquery.details.js - Lib::jquery/jquery.details.min.js - Lib::jquery/jquery.hoverIntent.js - Lib::jquery/colorpicker/js/colorpicker.js - Lib::requirejs/require.js - Lib::requirejs/text.js - Lib::date-format-normalizer.js - Lib::legacy-build.min.js - Lib::mage/captcha.js - Lib::mage/dropdown_old.js - Lib::mage/list.js - Lib::mage/loader_old.js - Lib::mage/webapi.js - Lib::mage/zoom.js - Lib::mage/translate-inline-vde.js - Lib::mage/requirejs/mixins.js - Lib::mage/requirejs/static.js - Magento_Customer::js/zxcvbn.js - Magento_Catalog::js/zoom.js - Magento_Ui::js/lib/step-wizard.js - Magento_Ui::js/form/element/ui-select.js - Magento_Ui::js/form/element/file-uploader.js - Magento_Ui::js/form/components/insert.js - Magento_Ui::js/form/components/insert-listing.js - Magento_Ui::js/timeline - Magento_Ui::js/grid - Magento_Ui::js/dynamic-rows - Magento_Ui::templates/timeline - Magento_Ui::templates/grid - Magento_Ui::templates/dynamic-rows - Magento_Swagger::swagger-ui - Lib::modernizr - Lib::tiny_mce - Lib::varien - Lib::jquery/editableMultiselect - Lib::jquery/jstree - Lib::jquery/fileUploader - Lib::css - Lib::lib - Lib::extjs - Lib::prototype - Lib::scriptaculous - Lib::less - Lib::mage/adminhtml - Lib::mage/backend - - diff --git a/app/design/frontend/Magento/rush/media/preview.jpg b/app/design/frontend/Magento/rush/media/preview.jpg deleted file mode 100644 index 61be74876a1e9e774428609b2e7a64b2b0e4a453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39314 zcmc$F1#}&|vgI)|#W7>-#1JzxW6X|YW@culn36$fVXA9cK{?fI5-42Bm@K`3?y)e zgNB5JhJ%NNg@c8KM~43`$nc0r$ViCrsHpGXqoU&C;NajA{6j#Xpr8=o5ipUFF|pB5 z(6Iku_}|UEbpueKK_x&Xz(7y{peP_f&$``@k90kNy9A(1a zSpWbP@Q*8j{$V>dU{d{;0f2Jj><~YYch&h9fSh?Y7)|rHl?^L7#D+JmZdc!|1^F+K zi9-rCf224Hj5FyU*x!8MF$m3lwiPY&H^Q)Mqn0l2pb-q&A!%U2FnRS8>Om2kleSK5N_R|DFZyL zfu=pJXtQMOZ|4Ajx(!KQ`_5k=baI(mYrTXwy2X|g00ID-2_U@6mz*}?snpMajr>~Ij@Gzsl zwADcv%K)s_u@>tVp33gj!L*Z4*Rud1(%w~?YRaEg8=qq3_%3$m<&Hd+Af%~gzBUjF z_!+96lw<6;FnjFV&+f6iHCVnt_Y{xS7Q(tA`7c|PnTpwITEcRFnpQO|b?1d`jp;Lv zp~j&XiSQR`ee6KE;=+-(YCbY%VOZ`E_+ZoMI_y=@d0$rjSZJ?l3d^l>iSi-I8>Ua_x~1iGOJ*5rP}K$2>t@)#T`P z=Djo2=-hD7wMem9e%y~&C(UWYsOY!|3vPz?r|ItxaNvW96?bafe3Os+u`U=t`k`5d zN1V`~QG-|P+mB6-+GpMi-KXRC2-FV1QlD8nIjQ zULiDy7hHGb1U|3LnPs9sV+G*VH5WH$)Q&n;E$(P=O&-^#`z}9HuO0ndPrG8+9f{v< zyxdsx@Ua{C6*j?@wi%IV3AnmH@bOLAY5 z9#6vhsXyeh;#6tL!}ol*jizUDC46$TjA$u&^al9q1rWR>fDWiXYF=xhF`vdB)8rTx zp6+j63&>nIzeTmYpxDt2ix(mi3ah{4LW&-2z^7#0ws!m(=`M130RRizcK0QOmib`P z)b1%=H7T)p!mNI_=LEp@4BV?ewr)0aA#2sldie}&uJCl*0`o`LEB#d0r}gW)wW$Co zuI&d3c^gZXTXh11`%J(nK3h{<{og(cC zvhMNg_T9Lpj;iA41HiY#sE&?$@tDNWo2Ljt!Dms%uy1I=yKKKSqjd4G9+O`5s6Qut zh=?+A(RgYM0AQ*oi$mwCUDsf2cCT+{Vd8P02k})EBasHoF68M;0svrW!E?vPVxoo9 zru)6Kd{L9ZwV9b0_`zK%(h+k2&p&*KRaa3clak#F9{fygsK$+4mC=s!7~A{fWr5n znPi}uGO%91Yx=)TK)pb|dI96XFUf=@YtH)>W`F{c{XjB1zaL>h`R#GKglz8TC(HK3 zRoFkaya)6WjLiW9iuUFsG0u+t3h{|?=d*d`1$R>m-e08wlWq~=s;)d~-V&JYt%sP; z`JDL0JI=CnqFZZ4v%eV#7(+HR)$WgF?&KjS+}|9>I2_VB$ ziIpch9|gxk?3vj4iAe~3eowyxPf59;62{%;!pbh7yRB&+Vg z(@5kg@qTQNs|TRvnTnd%dhgp{KW+d3QgStfaOka4y!c!?DD=xErEINTr%C|8JLO0p zDlGs$W49K~F}O3b3l{+2fDV;S$gpNc(w`6m0J5D2=qN^_cz^v!$l8h>>D|$^$V~jP zlNtc-bd?D4B4cgcXL({Xui?|~m@+JM6moO(c#u?vikol{H)2OO3S`9sS%$kpB?opA z!57kL@4u63O2;gYgb%N+BC4K`H1M$m132}Dp;I0+ouB82=f_`G$$mCbq$ zHvmu=5k$K@Q#Q2Fer74h>&EuboE_OIi|k)6EMaf zSAxO-Z=gc*)Ewp4Um*h;+zS8@g+T#k|8oJ^o>X;p8B5&&fTausM&tsVlE%!>f&6~I zhG-K2fc%SpC;Tz0`Tyu)?Jig1>Yw399#sxW$NMu{?wHx}Y4vuVP^8=&o7Q>D@Ex!N z_h5jfI#GMJ9mHn@>$EBpddWh-Yo!=;LD-zFKETf9jYR%EJJ$e!t0+M|JU;G ze`cd73r-Yd3h`0=_5dPHtR$!+CoL{B0Y~vJJ@Yq6v27|kY;Kfv!$hC_bIGQy%(P7F zmjOf*{Sqx{ss_J2aK9e+CIpp>3*mPG2shWM*}a1{bMCx8Ye9ZK+I}SP7MXZ~CRRv# z!KWzKoai~f8qji32mW{fJ{$PESy4i?0m@8*FC(tKefXX{69F}={2qp0zyXIOI=_|| z04bNsOff0=*zK9>+Emi%4S)$&u--b2zgj>LHs~+JMGSdUxKUXPNnp_aBobTgHi45dHYQ(4Cn$<|e>*o+vd-RUDa8sC5on?JB1tM1tDX_?u_g+e6 z4-T9l>>cO<+$n1IDH5Y5&(B{D?eJN0YW5zI!AM17NW@p^RCN1>&94O&8adNra9!{% zsR3ZChc#a<=`(nI0H5gSFb?VUhFpg!BwSYdC3lE$*6QMavQ+|L4%jL085wKsZj4Yt z$Wd@?l9G~yjPqkV8w!q2RY&%L_q)mdBX|tThj(58tjZGOPinJeyujx}kL)7@jB-I@ zzZxJY-+$UZn9UMrdeF>70U$93a+!eT*#1P|bNYf7NC^Ot2=StR1UmZ{!M@J>u>y1o zLP!V$@DhavHH$(GDO(5j*R~@Q;WS0RjY*gN zl2QX^h?b)h;q)dB99p((QYLecs|NS~a<>113S#~s7$_VEYK9#6yEN!poB_C?s<3wq zmz;h{{%;JTjeVHH2>U&K5{gN76=UIVu%QV28@{__@-n^7*B&3T-x$^5oP3*(_Wq12 z zj4=?Vpa#+8Hu9(K4#I0B_${SQ%3Hd_3E=vS#SR^>jDkY z+KPsogm>jtXyL@6=dHRzc_x`C)9_IpQ|B{z9+d;SLnuf8>N2N$g78v&ehvQ@{R%jZ zdDJe?pH36%F_5Qv>>Q_QGfVp`(bf6ReVMNgP#>~D3m#wyj z#|^b5lgI_u*J%{H_9jf9_p@GTuG%oNuQk$fsNzTTs~64cRf&&$c`eE=lAEH1o9IE8 zR#A7PzL6!jJS9#97CTEKvGY zszUr<+};3^RbQqh9^SXwC#59tL{#d#CL0l4v9)WmrISYxDBo<^;^!LMSs0Z_^^$y2 zjYuY$M@k8{UAV2BOdVFs-acUffklPh0P7xeeWw(x;LH)Kl5lw&q0SPPh&ih;xO7=9 zWWXY>ep$6*AJCH>o`Pj0ktU}5ellxd@wks-xqktz$Fn?MVIZt!Fv0oVIg$E~cx#Yj z6xrHGi=Hz(2kah(EJIwK!lIr`;ZZG#GpqEto*{L5!WpJ^PwEo6{-31-qH5h zykxZmi9z0WO5e@E39i}l!!bvr>35`^j4tC9`o~tIQa!V6)(eK4f2QvK6yI5uCrnm- z`7|9b9udT@oKvA4%*rr0ej|DA_7#KaRzEyMCO~zVrxR38CU8-?$C?`x{Iu4E~2)2b@}}_L7hsuIoln*N&dxtm-Uy=+kP`}h3F&t z8K)`RLnEA4Q2gUx4H{d+HFSQUt?>_-e^vTyVaU6W8#*Iv4s>=a$1180lyaH*rJHZPck)X$@#cU;}{g4rNHP6l0@ zbV#(5rPJOjgw?cLdc6VE%3u#8>z)o6FiJzS84il5dX!l)5>rsH?r zcaNVN`o>K?Y?OUin=6=)8TD6t!S+`a!N|st7wwggnkPw>{3_syjKXObbv>slE73ta zmEb_MC?UN5Ph_E*KAJ~0H=Fo~F3g;fkcvLw(ZGbUqg*m=J9HP|ekhxA)EYqE9~%I} zCw9l&nHk!xdIQkOhm)GEpYKKrudeEBYn9Dg?H0%ThwGU@9{WWlp}` zozm1LAeP-F!uqPIi{}TPt<*DcGcEa^E=m7qJC*Y~dh2V6i! zO=5plR@qBjxnh)gldlNV@Q3lMKv$t5YJwORU8|Ve=lw&uLRRsb&c!6di1ZfmT=@<< zjINxA3>#%e3q^;{7yJ=AuBC<+Z7YO(dLw(Lj)9?vCKaL4by10glbYe8>-#(f^KG;7W{smZu`I_+@3&EkgbfeT{~ z-4$2t+oQ830wG-FfotYkghvnf33`bK-0GUm>P3AhtsQ}pE7})M%UYy#2R@#bQ%Vjw zz4lXOm%8kP-xV)i2)DVDcZMXkxtV!its*2%{PG=z9aGw$j&}Pm#7)oNM|v z7BEs2z5%w474$XtG{%*WWDhcYc~}Q`Y;pFlJn>!We=g2vh_$}-x=)WUiKI0~aJai? zwqjt6NgB)xYa-fS7rhLxCx)!Bm=etNNb!?9`I z!|A7ez8aEa(4HStS|<&UZzv#t#^^v$?}XI?KMu@1B;@Tlf96 zXk!cMNqwRAv}xGWxwFW)mn)Rg4!xfwxZ)bh)|=!e?Fi8eH_f#~_{PBl|01$!ty!dS z_mbzU!OLa0RIAfn!d78G9u?)oomF%Caa#PxwzBvV1@#+%lwe@Jf_mC%f>aD+ut{}w zwo6}tM8@;wtU+0p@>+i8oJbxqnJ5$00~4-dHZoQuYanG)C6sTuldv(Z6P z00vuEnQe!^_DEpmh+PZROpZbhV+C}t?rO&a4y<&Kd|p?V-CAQ<<)Y2$3AJBbSw)^5 z>8qqO3OY6Z zqFD}!@hycHs75e{{jP+F_4QI4K9SCtE%49{Dr;YFJW%uZ#lWZI2JBGgb0ET%L>&#; zJ-Q}P?~XLun4R5BsFhbAWnZMe0Uoy}o{!dW&kvc=@hf+_1u{ap+;{JS5TtCg;wlB` zI$0gJqV=lr7!1r>1JkJ^on}*L43$F+SE5oXE7RF=^A%^4^J@rT*1~zC*E_<$^u4VogL4 zKfqDEMlNcPKxmbsWzUqN70)6!o=+%T?1!_aK-j~slxOv$@0vAyZtaSxJ5Lrifu&qr zRL6bJ_TJL2KXnyeo9KA82D`-DMp4g*eKw~B0myL_*0|uWh--@cFt?O0#L+;hmkfrRLJ9Rf$A z9A)rrb;OGco%%aPeQ2Z3#5AsUKHm*X7R}UYPrC40$YO$F6i;M)<;Akz8w=`&^h+my?$Nh}){0&hCZYFA z(|ygUBmSg28=rV0+B;;GWR$NWI~JWngeXbgL-_p-pj(u%o3n5ve&UCAD;)ep{KY*g zzkTSdratS?^Q|27%Jh;ivadY}F$Dt>*UJ@D5RUZJhwBsS59)^HCGlr6={a2L%6aTT zuMnuI?G~ShoNe9HiWpf&GM=_70|kkC4`MbHT&lWHO^3j@=>lW4C4-m(AqY-rdOps6 z45sT|hZ}nMw6hX@Wg+BDL)e-SsUhyn*S8e5$dx)$ljLBl<+m_eJQvNz?09}r2Pa&f zq9_!Sh0UZ*CR5BMV*2iMTOFM`Mj?zB!ggsgnk2BblI_$~Je+@!n3#Ay zT4@_A<^vStJ`M}3(3s^!;h4k4;Qc$%!R4BsWL{#26P0=ktB~aN6t#J`YVd7x1?5Av3zucrJx*JY;C5g(t)(V>Cs@-_ftS6ljeP^q zBcgZv-z9!boiQ_9hqH#=gh%RmA6R>Q$2?>Tr`AxCUZu<`7_{Dv9Q?t$_gHA;FR|#p zXiBe)xt~te;RqJaev~4D-~<*5prJ7e?B}d%(VzNpG`NZ8_=VeVXwD0~{1$DU*L!fHMaNNC7lAWYkK;>$tDsQXPNuD| zP&nVDYH%~|PKPy5B2g|g1l)MFzG>TCi^8rDL;7f;0lmHun`W%^Ow|Hw8gcElk%PSYThYCs{Yc??T50A_kTAP$odnDJdX#>V)#&_#_iV~=AAT#` zK<#%v;*Uicj`;TV$E(h#_g}>$`=_fnx*<%V4$bJ%n}+JB*p_^tSvfTiFNmITNm__M ze*rfiG9KZ( z&i$Xcv=mA_qW=8zY|MED95Q_P0ii&y=CRjwR2*eC-Px1SeI!|Sw4zD>OywR^PhHiq zH+XGgUT!!!Wc|G!v@D_aq2EI=;Mto;PG83F^hj+Jfl*2z1t;yF7 z*Q)QB2B0W@Rbvl|knSp)d5~7Avwy?si66=Koxk(ugmg!Bih+U;#$Rm4RNQi!>wDB3 z#EWQ>q0lPeuW+X)-v8hx{RelSX#m1HBhs6l_8f-DEO7)Ib5$gwmhgs9W{i=I?rsjb zhISF>U3&gRi~i&8BK0S$H$VY@YUd6B3{*w- z2q^_jy>bN{;yS!{vo|WMo^flSdtVutxy`mzDI1Jcc*Rs6kmy<3_CP-QKnGdM=6(a> zaW>uTsF+=9&z{-xJb0tjC?f`cbT`m+cZtawjeb(z(HHP!f~bnKC&fo4z6I5?xe@zjL<*Y*3jOZaIMv5vujCkzo~d_js}pfD z8ghKjBKSGmWFl#!w+9fAFii`fmm9RI9&~Je2=k9ZdePPW(+@_8PT=W8t7)mP! z5!=!S;;0pW4amDrQ`z%J;;(E_&nuQDV=gPcL`UM}J7B`$@jo$fg{_auf_S+Xy_da;efFGY8aWgkWnx;QH=P(Wh zA4F{ywT25zdp2ycpRa~CjHaX_J{j#&;RafFmg$z`ig**kNAqGS^ZL+(K<&n5oT^iq zui}JWyM&UKDI&QX&swfYR*B1&Q+XqoJM9eCn7!t4#nA5t^Ltu@CmG_Fx|#4Xb5bb(wH!PydnEQsD4nkkVQjL;K{AQ z+;ghbdg=?~Ro#ku^#dAg^?cZ9%nRPjx;>3#+rBlySB(m58ZrDQ&#MgjIf*yG-W8qe z7)R6Nx8|2zF3;0npJN4pvxQ(F;NY-OkdS{q%K~Q$Q9gjZqmw1#)3!!NC8YoA9hFrz z2~Nu|BNyF8%xjZfy~&`ZGj(zKUckr7EAp?YLl_=Vsc9wUnz7q+hko^Me6XW23VmpBd`!fntpa|ar2WoWpm(vYL)-?v%ARc)Ds-*Kn!6;Ub zgQlO>B*9QWXyL19-yW#F6oOP@Ux}?GMujDd;?ec5XrpE79(36`N*2K z@d(>GCZK?N^JdDBy@!PWO-=#x|4|FssD^?zKa;Lzu}Z_5R=j*wu-1k{yE`6-q>?z| z5+j;^QzUD`@CKMoL)9@`ZXI)-G*x%G24kgVR4gfCRj+0C8;c1^vaeeHhSSfg+_f~E zJ|kF}fIji%F58hTdg~B-ztBpD4+OHuEG*@^hJnaOPU)2ld2hT1TPFo=PP3jl=7mMe zv7}n>G9kzgU483L%&ED1#9zX{7TX>}md~z+j6g_o5MUVFs);ChtK$Pu0&*EUNo@Ys-)@=a2Dk0a!Yz|LXkxJSstJV$&r~0uHJ&>B7g3Yy&bwUtzBgOcN zjd^;<#=Kx~XIVOGs6K-@#++M(P}eBp6Qdoc4m)>>#G+ zheTa#GHxj|t4rKa@&;^5n^(cbEi~WKUOal$Ybj1kRF~JLmf$*DN~g$u$RA@Tvo*`Q zh6cl6U$@yGr}WC5Ek>Bi9f2SfHbmp*HLW?G|rn6BE5f7O%PwZ z9Z5F@h$bqo>Mbx^)`DYiE%gpufwZ`4s$IBJztR%B1xjz7CVdHlD3FEO9FU&` zIA*-keEwE+H(h>c{L@l>-r?pGBFp3uNO{*y)izsYl79a?@bw>4+D=oUM(v}IydT5x z{mv~wjc8m2M*N`ARax3iTuP&w1*GD|)}CWHkLDc_R2>9_85#%^!Nim2*^R#nG0QTm z8b}I%sO`1Sl9@Vgkq^_9eIIE=h*#v3Q`wcjf)?6)j62D?V`rs+>so}a<6j&rAhdFX zwTQogP0cRikk!Vlvcf~-5M4DnpV#sf=>ubox!LuOU5P|)(UFDHI#fO-rS|rPAJA2L z1QWT>d^3?HDFQAgwd2;ThjQ1BPlr19{CKk;{ zBTwFO7&1ad;z)|EH?@n+xMivFJiN@n8R$S(+P`dnc=Vxcyd;v8HDjShjJ)AO9W2vA z$b%~%A^3?F5nY;iqY!4eMM(3-(4;=CGy7*#p7Ij@1k=Izv6TX%(I~Q8T-ikb2d)Yn z_{UJH6jE2?!Mzykr?k7!fFqc7Q@t+tlE1vZLp5Q_@75D?msi_IRe$y!%o?T85tf?5 zJM?0^HeF6XQ+$y=vuER8<~w_87q|f@Gjhn1LX6z`DMFm>v7K~zqlwbaS*kIOb3;Kd z$1yEp(z4cc`<^3Aq_`$}8ujy_LkaWUukFK9X{|rKobsBlD~hE~qf3km4VYc%0HVMR zqKK%CJ+mviKtQ%LB=%Bt#yc3mk4QQ%lQJA+m>6Wd@4H7oxw%WWSQ1W_FjwpXB49-B zL(eUXs_hmNV^1WmT+~9*#1UbKse^32dwf4U1`y}6aX~pznOg=RiA0S#HM`7~tnCGo z#EmEv2@N2iqVOZRuPY|qXd>Gj4ph4=N`t;nuSM4A@#d(zBNYI&x$0E6pCzF#aL(sm zx>Hed%OCpBO=P|9N1ZUXRM9dEVlEC(1R!FDIM%X?C_)13F9fiPE>m7AOCq&|`H_4o zY&IT(sLEn>?TkMdJG`Om1}`UuYyizsFl*qh*o)ilnbUlGn~Ypsmw&KHirU~kYpQ)$ zEY6ds?2llmyr!s0y4lg=6Czsn{vv$_Ohn9D`b_#vMRul8W>OO+of8XbhSgr(>Vnf7 zqt43MR5)~ctrCrVw_^$=64O=BhOK!<{osC5PsDkl~iD5;;L@~N-<^+16hlKNAo zQ=G*JT@1wbI*4XH&y1D>Gg=7NJ+IQmGwEwOXlkllu=SY~`9Krv9V!0KR?x3@2fP_5 zzz_`Vyfb?XWHu@{a*)^-iTy!PM;VU^tZUj_GYsGr*-Pk$I*(3ohy)P!af^-+F~juo z@y@cT(C6eFFVd|Z%RQ>6v%SoAhy&!=uuoPuk3A^doahM(Niw~8yMYsh%_m)BGPDBv z%4-%n9nJOwr#-jwErj#Fzze=331HlUWs&Z2z!UkZty|5BPUHBs2L4%OSiX|ryL0yb z&47<>DKE0hs;fz^kL9})XK7|bx#d8rhMp3eo42=tD`ympS*;ll00b(ThxacFH7 zyH^TxZEIq4^DRTFORt)rEKMKW;_80E@o70jDk=^UpUjOP+!As-s!FEPuKXUToNKhbcjrf7VnrukgX8j_9Uj&ITrWfHx; zHYp^NldJ4`j0w!oqlgAv2VniI%ms|_P4lXL64E|ePYM`NY?HDlQ;IoXY7qmurwxR$ z%jZ?9<69#iHzDcgi=1@>lBqB^mOvoS zy}vJ)eH;v&n&-}enD6G&>Vo|c&g0SxN@7H+alN(7YQ+B4_+7eye^U}u5F5pc-oaWX zn8AWz0!rAC|fW5A9L0Z z6&4DPWx;+8@)|pdGE0Eh$AM%*lre|0l4KFau*92;J^X-Kg@aZ{@SfWMkqLV_xY`i% zM3oP*ETM{2M`P3bKD|24WYSPFDi3%_#buLDXD|V$h!N>W`54CN({#a;2wAN{l}(I)ww=w{g1I1Y++ zTv6dUd))3wFa*lwa%%<1j{7O%Zww9#JYc`e_%-ywY|h_(IB}OJAxSb>NnUMcP4+aB zg#1FncUuLMb+_s^*tPf_G6)g$O!JhPnL79sm+RU4AM}-n=0m8f??3dS_3VDrJ#wH7 zkg-3q!qpZ$Nrr{w^b|Q>K~>#+OH2m7c&4^{$9bNgjR>P9!sCgX7`dM+4&m_KP5%9x&Y1IQ@*G(52q@v$_V-cgfS*I`Fzkv@*roHf~VvheH8wN$jcwOx-Nd{Fo&yj%uyP=k}1`-wofHfN^PG)ekExoQ@~z7@h%k`sg)h^LT*MI%5A;c^wn~a z=?%bg;@iCXM6ph9{O|_Q>-qUQ6tMRagEacxe)s@U@k=^+I5PrNn3X-G*z4262cz?s zl5w!t0u-Ky&)w7I>7Pe_)+w`Lh^M!wPc!Cul)@p8Eow$qnG=~onA=B<;jO$jRtQJ>`ei|$ljf%2w!CVmj za}W#L6vLfmk7EG91mT>oEf0AKCdx@y$haWjV$>mr>1gm!ze*-_iYbF_=Ac^ zQMqv|TQ`$y1MpIz`oBq6Y)U_OG+UYDfKRK`A*bRYFwE^5Kk$3*0>>t?R&LDqHXant zBHu6U+*iW}oG4K3ScET#ibq;YsIN*}hO5k7R5U(!WsoVrAE9iN;;aF;HoUgu$+4+b z3a{iWaOHZw)gf!G$m6kvw$IZ|F7gFh8ga%87`YvlG+#W2XAh-M;HEMbF!FdRX~aB- zfyZ6wHFTYiz)z$;7DAAvoYT75Jp{86Hf{Fv#X5HmILLF(nNs7q2cTe(46M_zgcf^X zhWabF`H4mttNiv_CC2WZ2_XL2c?osU`S@tO@ANnh&N|zoSO|@=_6n_=fR1Hm=fWhI zN&}bZF3m;u3J7+g+%gfkCGUrDGH@a% z<2v2s0Veh0R!AD=eUm!6YlU&>wY%8EZ(WP9x`+q--vb=5VD+@sYp#`QAXvg@Sp_92 zB79mmgr;C-r9ZI?OWAhdEA1y@$Ak)w5?oaYF+>@|4!r>kjVQzMXG!Q4r1R`;*_}4e z-FQaj?c=`MD7Acw03;Ia&nnxOpvr}-wW|6UGV;Cw*ndn-xYQ6B31rmPXDNl-zc@XZ z-8D^(Jba!ytZM}S8pbZ)UowS+zAvF+*XK$MOCd!sN*FC&O^R3dDmv6Ef7sHXvo~JK z*8Dj8G!HQzcIq?@Eo+myb~B&}oa3U_9Zi!J4bSU;1H6k#Z(3Yw94AR#>=8-9bCUPO zKG{#t(x0zmmd$0rM%bM%h7Af(Nz>YC)0x!lzHkHv{R zAH1EoC2g<6y~Ia!K*$+26t5DtD+-%_sJQ!eaT zNQZ^LW*u|Cd{?=SSCdYXS(C*ym(Qm>-FtyE)v8J&Fe)U^=q`VSO|0!~i0Bu$F^wYD zBpe~hSg6hRZo0W0iQo961@-lkO0)%kq&&pjeNwNz0-n6t8^9{gyxW)h{A+u4HatXZ ze%PsGn~MkzvJrYSC)L^^L^y`(GNxzU28|BtGhFGK<;%;xk>Lk&HZ(6Pm_1w!oPlmD#LNqd`kMknh_8g~#>d4UB3 z`}NhLJ!!Lzbj_%;7vH@Iqbca32U;;kXKly^aVh*5n&)rgyhdNXvGJ;ePz1nkf)NR~ z#}>vc<0~gjFPdk65zjKFGlip#6}DI5nYKLfFi;uWdsL(?6Uguk3;~~d-lL4@xccmO z_3&+TURyGWb1%Mla;;TEdr{|TnwW5jA&p*M#Nl|2!f*N76cSN-3UztDcdA=>C%lzO zN&qJL9G|T+!8i*`ZBfxau&B8BbRh(+*|lftZflIjV;`P7hDN1NTHb8So?HMUp)m$ZlcpGT;wajxWK;)!jdTl zqf*(Ve-C+O$3ks)ZTDWwPvPNN#4f*f_iW??-d-Di!W&?)6V&HlmH~sEUbuHul==p( zDmtS{VmW=j^@)|qv(yG*&h6~&sj1XHOlqPsR_Q0vVH`MC`^3E3o=pD+P&_+`U0HX% z=p-8WR+KOfMf_cbolAdIh7Jp}K2fi|s`m!oKY8uZw6y1|nb#aWeMjEFYDy!i{LvjK zi2C9bh^tJ$nd4yZjC%GK zdGL&Seg(XZIH7|a<6)4NLXs6D^0yY=cRt47*e8AU@<_SGgT1!-vGj^bAcd?_TQh6X zh2hgC7};80>(fbv!1VV&ryo5_+k22Ucc89n2Ax&(-YIS=D9lP+PR3r61Gg=?-h8>< zf*|R4>7O{ooc@4+@P7t@wEsiu8z2^KRXiB8qtszsL9ZBpdGrKEQi0Z0N^THRjjgzXu)kR01 zMjV?-Vx{_tzPEISzF4JDnpc&fl)eyz0+DJP9Y%`q3y05jlHwa6M2|Wk5Ml&Y+#2G6 zV}0}gRwgO_8F0{L9#}hj%^{^o(5daqc;4DywPbTx+4q?oq05Q46^H9n zpkyurk@CQmxJ5$wY_G!b!7M>-lbJLF>y~QaiXX$~WEca2dn+T$c$CEj3$$}H+$!cR zMp6@tB&?CeCMNoQ3P4@$eRW>mLP$%!cvq5yb^P=@N9ww}B;nG-GS>jI)kPR&zK}NR z4ec_>aDy>~n+&!6N5IN(rAj_zjj>wuRF0?br{r%s6r{ql&=;c(AcQZH1T^qSW-4_S zTQF+&rJqYza>n5M+3aAc4qVcRHwRUWM-((m?F!L#Vq3EVYMf@Y#kTg&u{q1U&@ClL z6OylTR7$RU3&BCE!?II((>@_{z_)z}(KX%*8M%T5DZ!Yj%NI3*sh?&B5Xlpx3C<(t zvwBy@MR-*vf=pczkfp4$+u}gLHPSf^#6m-2W6SrF8~Ig0sxxra@}JGEX~|2n^vFBD zCa;5$O;u;2JEjkR`Z%PbyYW>cIAoMbh#9o#2b|p0HxnHY)^VwWr2<9GwYo!el&oa$ z@-nRvgYPmJMg~SDUM&o;(|nEeQ}?myAbqp;J&ZF$3Q7pr^Gw(Up_x3E;2o>8^8YL{ zkm>B_?o~ZgvOkU@p-qS*hwzG4+q`p11fl#)J`FNzQ3T8x%CSSR+0FO zT`&${S)WExNR$3J5bHw=O$0k;P9CnXeqYmac=Sf@7+=HQsG9U86K{z>r*_wiX(m55g&Y_M(Wn&TZP$*Y(pEBkC z;xW%0Vf`#^M^VP--s-;LGTw7SN8=$$H@reRpL?_ccam@!cysgdQPFQC#8p#JQ#fNM zw)+l?weBXiEZN-+qkWA4LM-kxfo;sj7vq^E#YC38uWNQ|(7tY5t-F%wa!m!xJJokz zQ&THyvE$c}tG+CfqmJujeEp`#wWq1HIX6^FiI#VFU%bk{&Tv!V-Q3?Iz`Pk53+DQ|1DvSMjEbUZ!~Z&G<*2ZFm8#I3auCH1BHDJ~vB7 z3@q?_)OIDqSVR7OeF1WrucK#YbjK#IHH z1H=qwKZV1nB4;C0K`|eLi!&}hXxxl+T`=A^KU}BWU%a5Xdsc1a?%~&XDum~~BOF_m>X)#tc2&>J9YZ#J|(p$G+=*!j&-xgmaTugnx%2gxKd=|Dwt+}}{+7075y zQ`3|V`*QS@0ck@Zg^dd~P!UUEP=1YS5!e6FXtI-xVmQ+;KMhSJM58e<^F)`3K67* zu*K$!>(0cPd{S%PXee1OIq?9y(36Dg$|L8vRD2mRv(F1EUzUy!-ch)TB*C79YkZGW7!O#OcDF@cko zBHXFAaB6fXlw6~JEW*zKmW+y?;HL__DKeU54eG zCmpt~XFub$aOumJ1X9{284e*;;XF_J*9IoHKv*_m(|`-g?};uj)>81#?d7DGh(0n~ zNK%BXB=;FnqsLD6>w*bBAWy-C0k)u`Zdt+b}$SQCHCbWdSXDE9UZ+pyCJx@O4 zZfJXqGIESlWU1^6o8ZdnM&s2W7;q2I@&aQO2OH3Cc>k_r-wO*9#b@gpdvw`$XYId{ z_trsiJ#Dw>;7+i?-2x2m?ydt2?#|$@f#B}J-DPlhcMa|kAcWwO07(cThu{01@4Uxu z)xA}>>ilu4da8G?mYLnNckkW(tY@|OU2Zm`?Z@pe$h}cMd9|g9>zgNx{Kmd*Nmn5Q z#>qp(=CQ(Kre~5zYop7H0(dc`SHp=mYrW&a?M2RvoPSerz z^NWZ7!W*pU&25NJK0}8}++I>DZ9weD`)rL z;Y59qe-kxiKXL6vdwY=9-?74xKIqTs>5pdz4fR@A8vp%Lf;Sk_MtWGA1)LwV+JZCW zTQeKRyVlCs&BwgS_H@H+WXK|Z)*;c2B!-{4Dhg|aQwY%ukJ%ZTn0OxlBS|&OJ60?t z9IPf1>*eZY)ZB&zuN!HX2?O~x&P}L0^$i{~K2w1)zq`4^hlL%zNv=B$vgse7ZgkS4 z(r*xzbdBhgr|>Pmc!yq$Hj8z`IH;X>7FICSA30Z}n}qBsT})6jj#R*=0yJ2w}8u+Mm8SGYo-tlW7}*an+Dlr4Xt z_{?JCZ;0%SGCA_0@F}1Qck+gf~OJRff7O?weYG5(uhJj}IP4#`2cit5#7w^c8 z@L~%2u`i$KwCxL4ipe6~S1Ovj;5bQ#6qHAmKdz(zz?$fRD=YTqSU)z#;t!2s5)dwb z7;{DUEo0J7KyNL4#(olG;0`wa!hv##>~3*W+%~b_C~Eq6k`Dv#1lw z!`lbHeNjYz&@R!B78zY*djje~tYDDS1ld*>2|lroNA{bGh&NFb87u|a`Hn0})Q@6`xN_@!jkT`imF%lWsgq;fI1YcK z>x?tAgaD|=_g@#x^t{7X2E*p2b&b97{P-O%E|4Y5*jhobj;t6q#@tWjDniTN!tWx& z-_)RBNJncf_YOoaqZIMXb@T5_)nA~-po7JTJq|CkA~+SZ!O#8qs}(55%5N+gtpFEk*P? zr_NR5nYk$k*dctdr@;Bf4F6?!Hp$c_z<5Wi{RoE!%DR5l_?#li^cO&_o{KnB3an?0 zx*P4As2*ZitFFu#*Gp>2`NY8~ccx=O%Y86X&CA4KU|!<;A^%w@3_A+TA1f7LY*3@C zVg+$UFtE1hCoPLkK0M8Tbj&7Rxr>vuISw*s*t8_0gAR~n_A7?&cj|Pk)@p}fCWL(| zg#ZXh?~htC!~KMsr=DvcUTCc;DH`jx_$)7`15~`u7YbeNdyNbmg)=+O7xu){mUWYw zrh52~4TD%XGU?42s1#QM^I}7*n(_pu^uQtZneW6DQpZ8&9qC1FU{IqlDi`ZyWTbZS zerl9~PRNo=jj!AAgnl}}H+gLgqEjpIWzxK#wxZUd*}GP3c%80wzabuZj_hjm8!k3x zxkbU{#}=#wM-AGbxa`N-h)Hc=<17QN>`(v&KCnt5nw!R_i{Y)c}aKvsq@YQ0%{AhkVA8 z(p|e0=9B7w4vV-p9<>{|#n7=OCK$N@jv?V#X$YwcKUd^u8LBb>i}UEbRhg9+PmugJ z^bur!5k2bj9$-ne$m;K|yfbt-IS8RW)^sAORT^yfrR&6!|A3d_4aL%$H&h|d%2}z8=GQ%Tk>0jthyvO>4Yu4d9eR(5k z*%yZUlS~bxDoh{i^VKYiL+q_~>e{a0oOH@srS*r|Jz(vDgETxi^@n{Ib_)986>e{U zk33#G>jG0OdCcvza{JWFb&{@a1bkPC;#MX_B4RL|<|DZ#$>p2|-vz{im%9?%&ZC_qk6UCp_8L*uu^8MSjqj@OY1$ zW}23r&zm3DaZq--b3d05l80=-0&gE3mZG)*ws|EiN ztd6T4g+J8%D^#oeLML4)5so;=uYN6x5k|n4#rA%sd>Ml6QtkYMQI-YUw?Pgl zyx(cGfyEGVJ07IQgQL%YUEUk)p1tGktotOn?R6#a<($u(-!|Oj1w+%XO&Hl!A9{`= zpzl0^IaduazELA5=zS`1HRjDknFJm$RYG8U@+S<2i1 zEi^Pa4TfzOonbFz7}Yr>_Daon^$82)U`_1KI7t_fxvS{2H__+DuwS{3hL8LzP!W5w$n4)SU!R5B2bz?ThtAIvA!#59L7FU=sV{Oz00 zdb=R#@&wsFiuCtifW2!Y#}*Ub{0%mhVX#f!{+`Wm%00`8&JVwHbxWWQo#mGBO=IGO zS9c&Dxe`D8{g)4WgEiMhtA7Coe>8sdv9cEynv5-G)D3P5zxOWfL9z0-N_6s#lxTeO z%=$h&UYK$k1^(MhW!V0$zTA$`Lg5Y6(QxAX#ufOT=3jt`k#A%mUMh%8z$8YCsYf6v zyJ{IJ3q^|5Va*keu*UkU{BQA=1I02KI&JmTclfA#B( z-w4!W;?9?dwY#B9M)P~VmH)(X9X3;o3>|3}avZ32=8R_TxR_*0+i<+((Y6m(xC6H1 zrEJ6?6(F^*jI!NeXdMOvcTsH}#=QzG{QK%{vPadh3UWN516k2$7^m&1LCRTE3c3n` zU2(NA|Aq-@#t-3cl7`MxKM9 z`3-3M`yWQi&u8qz9#V!Pv#_xTjnbZxxfU)jYrUWY`{Ba^ij}o{wfR3BP6A@1(Xp+^ zmLn>%myWat3337Xqdu_?jy}!MS&s0AXrs$BoIk1*2b8D3)%|adT!~uC84#Mvc~?xc zeG*&x!XCJ@9~w1{eAGX!f4y#r+!Z0;-6n^7PY)f`4{D&PUtlJAR|!|y-e47Rzg}J+ zQpdWAxxSoNV$6&Kowy}*Sf#@J%%{Ei^I3R7g0+NW8g-C+ceDn#7k>Ps64`m7+eXfV z-qR76j#QRz1aWD>#J6p);QiL^Kdqa8zH>UX6F&4kavbTNzS2=c#Wt?QIh^K_Q?atMbk*Og^EH=G0q?bMDaZ?XMnfh47ksY&|2GBOnYj_@! zB*WgW7!msj$0}XoNGw==vt3qc_BtxKFch+57r=C4UCf~c&H2h4DlDayj{_&+*03mF zcc`aw6PAxT1iXI7(!WFct$O`D%$BK*t!o3vTyki|(M8AYj>#{PMn=;&?5>U?!|O<0 z_vm`+H-;8F7WZ>nJC3HQT%%@TgrsQKd;5G73qwe6r1lK_kr`Xl9)F1$Bi0T-;DLhW zZiS|WWirdyw3*CGS4hx#Xz45Q#Kh9lhV!A5S99f@&$vwmlKs zo8R=OSvDjRXiRviYx&7d>Gv=zEV;GC@4Qp1PUb-)%sL}bH_KFf`&#zVA(;D_Pz6d8 z(q5fvI(CyVmFn=QST>KJ)^7LDEq>u!_C~!nPF`M;bQ9Zd;nF&BigERlvM3+V--c!F zS$jNf=zO}4Y8ryjMvbd^#r#*Y8X!D0&@qT1n2*GiC+P#Jv5wXsOegc0~7Z; zvotTr9b|Y!`L@K6q(~JWb@Q|cB9Dh-5V<6Z6Jx2b)dOFTv9|IuHP!G3PJi=s={p)? zfr|W=Zni*(qN7~wNDz;mcZE@qDLdH_B$}%uy|W~LTvO}N(1*g254p_45N}>JNK+_6 zM}GMvARp43V^N}n6b|1t>t8*xzSydS6@uyLm(kyGB|q-NLWR?A+Y(iK?~sx+lBxD0 ze8WePg5hsk~qLAXXiiOh*Co!TTkO6}w zKlPijiZ#;{a-%$l9pN0Xo4(W(FqVX~A*eu;#w$20G~rimklAdWJc9<_3hW&HUmSft zPZ)(%>-Ql3Gu5vdVO#F7$Wdu8f%?LD@4u4{(&ovo$~SI^&L)sxNfNQH+p2sE2~FX` zFjQ_mHz0@S_EyQ9&7!)p4>`xN_pqd9vww9cfUBbZcwhCT{fGJGRjfz(eTBqw(V52o zgoGD2+jG3HA3{Iiu6v39J(~WEtKHABA)C7`H(g_=MkiKYh+Ve!r0;DXxU1PRPeHbA zr|UH95D`lNIL2`~Q8kq*)8i{cN`;|L@ZYteuFPPbz*A;)=J>*!LJ*Cf{gSSbE%f(e z`ZHq-fJ%lV=TvvfFCEP_;gV6rX0qnuL+0H3eA`?z)S8LOF1E!1N71heK-(ChI+pQE zRZ*Hu>BPr{feC}LdWpk0;bxWv!7-_QM<{^I8b)MxuBUz( z7_&oL`aN(Fqm=5-U>)zQy5H2s`_45G`}aJi93wE-m7qDp3Ym8w0JZhZ6Y|Y5SAh#W zG5Yx?$MrzmW-#Pe<_p6AE~!>v5-Fx)j!%Qb+WRg@nr_AvE0E&qf?`>*4>u3P83)Hq z&ZInUz7B%0JJr7{d7!=0U*l+*93^SEh6- zx)A+w8{IPdFBl!SeDQ3fdF$4;a;-+P$31@mL%yQwUt@RgL<-+}j$Qi|+~D^g{R{d7 z`=4??;&=Z?&c6%)AAqP#b2gMrpOkIc>jtuNv?eT{#BZc3lx%Zp6 z8k#8E6sYnZZ&U?~e*~_7;bLhdsuCM0t|x7hZKf>2pTk|E(sI=`sPX5hYZRlCa-h&l zbTs>QaxsjINKg({=^I2oN&U=N?rc;3(R#;mg1BsGu51d39IsdrK8?o@f6$rEfkUhx zC5lcKb!ka>uG!gid`EDU~OAOVn0aTE@}=%!=sN2MY=L!5&g&{b1~wdKtdo4yFsB3)|H7X$Up z%4R{bYyC4D8GixccB63A#U*lv!|ZV*_;SBXZDzCZ@xH8JvPZX(G>0h@wmAFLczl_C zy{SaF;G|>#U31LaizC-OW7tEA^~mF<_0;Jf;&aoXRcz#2UPrd*9lYRLsrjYR;R>#> zwy#`1s>oHEq{GbBaQaZG5O^V?cJu2M0CU0(C*z)F)B!z9dpDdd;z1Df$#tVSh zO3mezVIPODRi-+9Hiu~>tw50=g|2j$^&uGWss65}E;E-9G%L}JwC3rsd-&0PPQx^= ze5VG8oWS3O!=rj0;YxxpsGVtwZ)%R)n$0i01!y=!8%tSaCJE0odtvjRB3wPXeX%2o z{tikp6r~;w=pVBR91;kt0g*9oWtp17 z38a<_`IeVG1t#$N8n|^ z;2FOPk}UddPO?}*=x}%!qfm?}2UWdWMpgOg6bK`)r9Qkncgl=7+G{%@G?J=g`K;rA zqdK)M+Mt~|ad8ADzAU+x=Uz{}-J@5PB@E;J5u{EQDV~CBXAqk}$HaTOx_ZW9__@8n zhWtjg%8oZ(Qwmek*{4bvwWE{m{uv8Djsq{6v)9}!r_Lgb(_TuLD;9aoM_x!o8^cKK z7ll(`bib!jOev$zpx7ZBBa6S@+OSvRW_$do^y{sGMMEjv_Lj zd!4%A=d$$dPeKF~M5SCeN0yFJiCZMzvCcYz-$KN-wd@IqmT~sNkb$;3H19ZqHB-i{ z!PRwha}0a-4e$_UD?Ntd5M?th_?5*PsSZ#%iFc^=?Gs&$KF}>^NAT1iQ(?74<*h1T z2-)BMM1FznhQ`Sc<3D-5EMwNgdOzppr^sA3dbA~C6SA_`;}0%@T)yH9=8P>cy7R>5 zs~*|?w?x6&{|5XYO8mb|42UUhD#_l-VC~0E<;P#L_ zPoSeEiXJ?mN)>d(CREPGnTbK$1u z`wXYREX(no=BZ>nvEbT9eB(@J84PE^wKo4pRk!-G_~t1>{2LSDi)17^zodJP@~R2c zy?h3)S@C=Z|7qD;G9w2(LS>@2^}%0vhnt^Pi&p;wLl<=cs&ccGO>t?w9IOK}ddJ{V z4Bzer2u5#=DfLoZ1TDq3JZ9x}h3a?@BW^4igc5f!tg5b4W)8)SMz<7)x-K+GYH z+;yKV+xIa^5)=E{G-_RZyST^jra=beqBm~D&R%FBC#rNbdS8i;mC zdc*-O7K1*%S#Ia3S!?m3udIa+7e4FvYa{mI#;R?^+oO3bd`?xmXbld)jp29wEzH@%+;-3l7 zI|22mg+39>fiovELkE+)PQD#pJZTi$%sK#PBegO81-5X55}Qf~>MdNjy`_LwQx<2NK0^+o~RS};F{5XAKo`x-b4bLL#(!X0XxB`!bH}~27S0$ z*>TPh{RbG_DmHTNBKs}Mz9=+qp*T2FOGK?Gr%ODbi;5$>gB(dZMemTUeY5&< zZuw%2c!J;XSa@}*Ms|XcgX!p<2o$uF=5I9WhyrTiggDeR9W1a7H5(3h zoos`*_EiLAnsz}E!HbZT7@~B z3Au;?i_C!>v5);DSCMN6CI1{mY@Nw+r6cdjER*9dRG`{xy1V5>sPZK#A*X)~`#m1O zsuOCE$FMtg3b<4c^`yN`A>7ZMHMZ!9%>(k;(cpBzp@pSrFa|F_?b8lm1gM0$Wi(^#s&`6el)T?IkUd z@o6HkF$_z*alHn))fmyG%_kR(Jw|BX|4Pj(aaM7tT~U%t8)kKgq0o_BCSi4rJ|to$ zo7MzZd$Ut`C!VtCmdQ`n4bwBV;TV|)4rJZa9+G+=7NZ|xP(P9}Oph_Du6~qg+Zb)R zcCLkY(%|^W%9NP$GBk&v?+719VZ3!(Um-H^fgc#2}^g3?Ce>m~`Wl)f6FKv^s%(%$T%VoiE+ii-pEcdOs`JplO zzt5#su#EJG*jaBvU9$^zSNVtJe;K%)EIYx6A>{G>237kJDf#f&NPqTz^@L4GS4vY< zYy#J+V5&$S?jn|`Izj6A4AIB;f)UE^2@QhQzEx^-TG~-mT&i-Nu?SB*@c!z8J_MIj z!M0!p`G=w6aW`-XUM8K^5lDZI)x}G)t1$$ThHI2qFDRcTpSiq{nFZvLj{{~`#hQM| zwP5(O%vcI~__4l|f8^gPS!L=^JbY71s!BM-Jpt{?V_V}!S4ngTULpO1{xi&OPm(}v zJiakUKBn%5Z`X+%qR zDnBVvd#3P}`*JV9k{KO-$h6a@-**|BVQ+&ibcUV`>9xt)T((s@!mN@*Jps#r4`)Kg zLv1UW&IC~^Tg?O*tM|)_n%lf30hJBh4CPnKMskiO+2)Qdn1sb}D)_$C9Aveh6b|x0 z4Wh#=VM4^is!q8!<`-azw!u=>;VRs!^)V=5pRUd#8L|iVP9%%r0q-=(+OOk$OGR&a zsd>=u;tExw#@|%5#p%SqY2<1lhLsk|5FyN0QdihWbrPi=N;BU5VCXj}NN29Gs`&G! z1dIlbuRSAbyMLbPDgW^#PV=YgK1>4+iJPYS#xr~TH_rRS@UqJo!q*W}U+d&PdKwi$ zQXvdi4P0PI9Pg3#3`zuuAb7L&iYFEF%dcSFqiN8#LS z?U_AXkN^we0#1NIoxet z8rK%5UHX=>^MbE8JvFLr6|2xd4QY8ZUC=xCAXw)#$bQH-)I5QdbqB@`rr^IXpfNca zqB1-3;*c0SHQTWFD%!AzF8%Szbn<^;C=mS%z%P+#STTCStNk5=-|qH^Ny|D6o|#5b zcse2r<~gDoe~+sxN^1T;zhh33aEMO97yw|C?WmjB(VEwrl?`iVBqN0 zQ7=rQ{|1A#KGd8gnlo92UvVY&x&1uJLzl_Vy#}_wog3o(7b1@QG#MHx0 zz)t6?@#z~nxD!FeswMY6A_C@x-XpbYzdBFtO)vmo3Po*F?r0$xckBrzlEF19A6!~5 zhPN|}(>!9_B9f`QS_ZMAdgre@k`M87K;MP-qu{ea(m*VUvq&rUT=h&Mm&j2OMoi%# z@xztxj{!8&nOA(L(H$Uf`%LVyLmX|tCEk^Cr^ohbHMkG7=7P(Z`m{^d^<$MK4U@;j zs&z&f$fM;Az6NW8ADuiAZD;z?3$fP}*d7zs2MMxQRH?6=-H>F$-_2ME%rrZKja+mDjw}2NFeq}1;>UgN*-8hl&G3a}@FHBeWo@C;?xk2gprr8;el$0TM zLz74tCgWR{YTuXa@>=b9j&iouI+omB#w<XMlN2P#;NO?mWDpak!v$j!S4i(S`%XuDHIfr`uOdbhY5RJIt^*nkj+=7<Y{vo8u)#0;R`{oY|l@kDkN+xjn> zGn_wa-V;PrlR5w#V$u=Ukzm`MKq1gIvbncp)0ErX zC&jRmI@T7~96}kq%%nLJIEsF?oi~EB*jm<~CIqABi+th?scAZpBic(4L>fHp8!fWa zutuEBnb6xqgKTi}b>T{eCfK{4kp}bAxlsAfW$?xrcqo&>b|m2&C5Cyq+lGI5<3^Fl z2sYSE`&bzJ)8TQ@gNe`B4y^`3<27s9>q77xO8xN2%2l65G6-0afg#9ih zgj^jAn&uuRq$jv*sPw7a@RMe$;$i!ZNYmEqm=0tWK*M|PVdE3<7b@Aa^N%jZ6Z($u z2v{jPONR0@+5BxU%Y#gHCF9ck4qf)_+jXo%{i>jcD;bNBe$2k@9*8#Vqzuae6~hie z_|R>^W*5Qngv5d`pyXxB`bPSN33lO6_ z(ORo@kpkIM2VGUA8?R?WmOFMlq!%Y#DoE-lCT%V%uZ^eddy!YCfj#)&WmB(v_NfQ1 z4WvE7={Epio|#Fh%EamS$YuGJDXYRd1`VA-ESmR?1K-l7nL18-l)Xq-&mqTp_B+*v zEt(DW9L|M-Z!MxQL2md+M_lyvaJWHQ3C)> zSO`~M+`j;u)+pksIH=p)C?2D^eU_R@nimC~7R#pQ2TNpNjEbhWXhxC*q}nmNm9+>R zx9AO+%8Wy&Yqcm?8B((GGyuzv?l71vy&IR!{KNC>dRA22$rsIu)2UZF@+->{R10Ww zROKo!O!Q9zIYt30qzW5(#FlT^172|H@gZ4y36fJ--sWm}MBG?4@x47a!n>qg&Mwqp ze4zN!n2eA$D`E*Q7sMeR6C}!3R+B^6HYnud~ode$2OWGFm9BUvGdI@YZbJ z4U7youui1oiJE3&LwkUajko~CuAA{R}EtR*VUP?2_j{_yWQ68qE?yss6ki7MJ9 zmft?f{skx&o#BTc7hT~XXg=&0pPd4Nm-~J%%4&Gc;rTo>)}PE z^PopD;T31m(^AIC0!Qe;HAj%*L}5|Zl%qnTQrQd)*YW>N0kU@> z;2U4>IIGe9TD&i;cwHt_TgC)BYo0-OMRrv@@9j@vRw-WM4nt2Ny)H!@nulN-Z_c^8bHjJh~6x!IX^R7L_q3@64=EB(c|iZ(6Yu~d*w{lUe2f_HhYLsnr5 ztdhlHtcoEubvQJCc@#l!-7~%xW@2_+Q$mL3zZQXB!KVmO4h*kT(R{l?bSVdADl$Ti zs?yW#TDL3!&){X)!16%$%Lz-STKm;^njvr~NPU@09ac{ma1=K^R13 zahPkX*iXMEgdB!hWr~Oz+frkQ`*Q=CHUMf|NL<`~ve~S3mPIxeebXM z)19df}NnhRt76fkoaL7p1B{ey+Id(-QB#NuQkUIIpD za3kFPgZ9&e#(rcI{8?75gce3c{&Osh_zeygojC>OAB|sigr!R1M?R7^c5GOmt^4;Y zKmE|RcVgEk723nF3TP&G82&qxsNy>-N&~5uhsW-bA6f!e>pGi$I97XlrL_#5r8SA3WX2E>C0hLNF z6vhbHn0AOn$5#Z-qKh#ebYIvR=B!|*tVfwkU(V06FddMZ&)ho0byjIFQL{3}Yno)l z=+E<^lz@;?>moO~i>8kyz;edY+k{K&uIs`M0VmYetChB|#zhM!R#r*m=91DBStxlO zIjTV>M>CP*L1kAEBOP-H-7fuVAg;3wQ)6(mm&+vc$fjO_4Z7~s8HW7u>YVW~FnQ6o z?;=~kQ!ySHH(EDYe;Ff)laEN74WGt-dmOw$bH=sIPR*A4x^svi8CD8wo1#VEB~^qQ zvQP6FVQxmFLQL_973!$lm+UyQcI-y=sMF8xF*RJhdj4uMCgnjDsL1_qZ$y;p-(Dc^ zz~W#3ZEfW2;MOz~mTUTPd4&zEl<9^H)|#`nEU)+p1l9pW_(lkT@F7qQ*Hj`*|F}zB z(1o0=<5j%5ZKUHbAgGcd`C%Iuu#52SeceVnb#rlb6$h*tceH}nlekobJL2tOSiR>1g?R3r&wGs3WP!M)a0H zg9Zi`WtNbwTJW!~Gdr3O_ZI6CNX@hfm!Hm%Jxq2WET_JuSr^Qi0@*9S6dnHpcq6F2 z35(fuX~#`yD=C~n>fo{xmUA1(oG6KIB0Bt(TM#m;2Q2cqu4AATD=_6Rzz|Lg`ws5l zJH8R754Bf7DttoBd5~Et>Dve$={RkVeuj&p$54L%qHSyVmwtu}0sNIV|4?H8p~_}FyeIw(=>6yDzXG7YfRf$8 z*RF=&lz+4M{yga@-9uQnAjehElB2wMbttfX6GbvE(B-`&r)%Yg zkl6(=+PdZtdB9pjIqmC<@6$p_nd^Gk0wyY~rtLD=@RHm>DbqYSrSqEN1N+a1f6 zVSm^~NyUjwuV%j*a%KBz@>-tA)3JY}zV^Le(hfp!5MoiSS6 zj;)0Ge5aZZlI!<9Z_Swlc?3CGMa(Zhg2&0s;fxuJn>Q}hpwJQmQU+mUePA7POa9EE z2o`R-Xq>?+^an#m!|LrT{iq_cB3me&(EivbL}hCk>n~v0n~hVCt7dC--JE(kDn!Kw z+>Kv66fXnHU43YWZb&;LjiX^$M22{m+R!E9$-3)U#a!b8?u9%5#OwxhTCxcjP*CU1 zeQn4n{UOT|fP`}pGT>g0++qbTv+yoHfk#4wvrQstur0>|bQ(;w%~5z{EXl{JW1_4n zHSHQKW$-`5dI;8wCOK^-urNEid)IW#GxTvJYDM*|R?aTFcJaz&H7 zp8cm23<#kgnr4kSkSC%3avOg=``1hhg)oozd~X_Ddkd-p7`ui-c~(^ zB{aK}c`Fb>oi?<&!Ur`A==f-hk{gvxHkPbOYnu@X65-;us0YOTp)qJRF`vRchtyyl z%9h%&esyQ+`GNMZsw{EFJ#b7Lnv4}K7*5UGf&CqH!x>_a;qiz*SN?^7slM=oQ4~C% zPW-qX3{H_eVK00B2J3r=hd*I_rW@W?iAp+Y8&F1b9-k}4**x!juNEZmqp-cgC#QK0 z^+qWZ7j7sro}nFdgUa^hf@UUPLHg4+5}AoEv9SDHhp9o2hs4A3Q=a6FOO29}l12qy zTG3CCng+QjCt<>ZBAwJ}9HWIF=?EjmheZ9wjtH|CD*sP!y^OPc7Ooo4r+rx@s$0M3 zT;+s!?274}uhrcb1r8#GE@8i2A_HBlX+I?s4VmoIjjAnrWd-~NtiMtwrE&k!$wopz zLqtVFK>9~=;~$-D1VlU)XJ zBGSs~onhU=9q!T@Tc}EagpNW_E&fzJJ5L85yjr<=&Rp2q-6XdSKt#xe7osr%mq;f+ zN|-KgyVGo*qCICVvo8GwL^PHgl>G&unJKm^yxI$~JspJ{n~Bn8p8X z+UxinE!DurP55iOYR$8id2RplqxEO{40Hmf*B=kZ-#Q_u*|yIapj)XT+QZ5X_vjXr zL6Iu6sX+fvev%^O_vpuR*%&>*TqM?-jJJsy(AJMFbhM?WErcE}O+;<~)J$nma^OR7 zXwa)`nipL=1+f6|zCaCLM7c5SNxq@%AZoexpe}Pm?1u??S+U3I*gaWMQ_@V4pa1<8 zTXdl!xGsv~>*iXAF{fdYN32%WhiLgo=?xTY^pEy|3v8iN!q~H=JPIk52|*!`UcWzA zIk~9cYe8xtd)`AeBii+d+2%&DXzrS2Pj&XxMfVWp0f%rwpkc;-?373PIt)?{^UmMc zb1_asW^m1c?YBZq`Q*#2I2${4{2ZsWo%L$bt=3#iD^_N9pD>-j!oj?=$lqM!wupdX z+C7$b&7$EcUT40O>a`rakzU1cvXNoMzvp{$hnB(+N?*<(vSNnz3p5x)^P7k!if$Qw zRo58-sRx<_(RlB8jb?(+ewLV&T|O`J=DVll*onu*6E79AdOJDDDy}9gTi0vK*}Fd4Lwpz7*>anpwD83ePvY&1ERmt z?;ATY7hC#HIsq|n>75J-SY3{^ot%%f?K#~}dl`gEjY`>FV!G_b?fYIoud$>50q}Lf z^Sjh>Zte-L} zuFaP`D3kLdcmrPorf{{SCSSQ6IQLZKgcMJbhY{MPvj(K^fZ%rpWY<(kB^TvC%9*q7d>e>-iU=&vT8bMnIp)u>mZZPAc_|zi` z;}Sw!hFE$aM!sZ9VzKN|Bb_cLPB2 z+YipX7_(?>h141$_X+EhJA7;we`MC)#>`$OOOR*I?r5~F2eRXOopX{%txgGn2zP1> z*Uyo2aF2wW_Y3@Nwyx$Ye=~sQofhz;P{#a0rW-Zt(}M&b^(|sX0?RMX;%qnlo4+S+ z#jL)2R~6-GcNLJ~RM~97P)079eIuWG)Iqrx)RM#QBqexwN+(*j?XYO0*aw#&+W=KQ zuvv2N#>U85?t<3ev92UNi3-`UJE}1_TcK|*X|0N;rl&n>q|o`^*s@x=5#NA^ zzRN?7VkALHm1aXIfCCs2gSRP22wM*&4q)6mYYQW#*Z3%rZ2RQwju>J^&w7c4)kD*b zMI>Oe(tDgPziw;_jxQ~h>TVL|*>;Fhb^wZ`0AE0ug zwFW-gBg{ER?9+c%xmT}Sl$g@em#=m_uOzj!P-fO+11-t9AoG$ekb!S zFds6~Yn1Zk(j}b5@yD4fxv9C)DgfIiUt|W~iRD?Qy6?aG7eQ{^{(Q_mfwW6dNqo~j z<$JxRCRH;J!`QQ5cdWWoZPB);CLbm_4;|%m|x*B4Tcp$^^NsWqH#S`j?r-l}p*vm8-HHthplB`T_4?1AKZDu%Bxv2tOj#&%QzENKACt zwAR-2?R7Db!p>o-#4HrUUzTgq9Ca2OBIE^`Sk7)`fhzse621~W?=tuhAclN$WoGmE zVSKW(Z$!1xdM}=sNuM+Ei>76~e=*@y|3Hxm5Y_!Y^o(B^LOp|6J5K6h!`D>2;7G~9 zgVcu#L2D@LHQN(c&ds13m(&=t>g`m`>p-cc2?|QB6kV7m zalf0G0N2mH>q$a>mz5rdv7sO60oUA3b#3^G`bOn#AbwUV1tIVnrH-R{e%3lH_0@|fIgTxm^Y*L06}o4O=fnPf@{aE&R~3q=DCKo}Q1Z*{Q7%hN22ORz$?bl9ci5@-`Y!=a4YBgAEfl~k z7@qgq>h=EsF$-{| zBT#a&;qVqSG+<(rByfO&LsO8FprY~r+5iXv0|5a)0RI3u7{@rk5>wb(V2D4-7Aq01 zPE8U~w1zZ_BMe-*LtX%^B5*3(V>BEA+9eR9`3I9!=)16|({p`bM-GAi0E_nuqe-Vn zInl3Q;uW@=MS+A3X79~u{{Y^z?gfuJe%rDB`JZ{einm(bP0azo7aR|Mxzz)-!t%o! zfA0Bk*)6Z2NpHL$s{@->#_A0}S(nCElc?5NSwxY>2$7U4U9t7udD{dI8B(W$e%qr` zx1!-}!(0eoIX0acsK{yLNWYJiudLGI(eG5Lv8`tv5?VbetRbuQXNZQ~u+8 zSDGsHss8|RzAMcYdQ|@axZf4#ioGg-+;58WMP8LZ?l;AGqOVGy_Z#A?&~+zWbQ9D; z9i#sM0xJfOvelr+Tn0y8J*enwA@@7aiaHL+ebd@e@_x*4z1#UMFQWyS;I>O0o_Tvw zX&#=V?ulHW19a*swc6tY70MeR4E+-*z5b{ta81#clZ7{SCg@vH_ak`;zE9dt#*g;| z-zIDAb9?*{@@4y8I|_f36+PF3yb<|^)X-vt5VV$6jEV*GDsMM&d@fFF3uL&=*k#`kaJzh^ADJGDE>%Qy9Qtt+9a(4~EZ>N@Ar z0|@&hVY`5oP(15diZ4IXdHNEq`zv#7x3doAD)KK2PXPKLk9FUVp|j|5T9|Wc2|Ebd zsSB+Q$U>DWQ7W0oe*~h4m29%Mtun%36{K`+Dh!ss^Rkaa2n2nU_#zPqpocOQxA=OW zbo2b3tPT1{?+?JMjoyPsz=I04(KRW#hu$6gJJ4Y!>}ymsJvwc@XmoW1ZPq$N=+g&( z=B>8+HJJ^h80^O~lP^3s2`Er)khCwu`|BE6Vb`shoHGmE>HTuU^Gb@U2#m z(JIqoWm+ZmJzxoJa8st|xHY0lUT?H?4|j?-D%}SmXQ)gDyH`>cO1%emE=gCSnZgJ2OybE*tnVBhf*M&t4djo8oX%0;TpB|s`i)FGV>|sPd0)2 z#_tN*^r;o1?Z;DH^iN*Y`&cF!EF=eW2kX>?EKT4U4TS7J_@#wZWtW4t6gf(aF&rz@!qX3`jS7V<-wy**SGbs zqV-YA#^ZhCs3#JMPS&bZy5iipIP&@zPVa(spt6n8h6#)=APuRO8bP2I%D34m(`>Lt zzGH|2P6?%iYKTp`;i&=Wp&kN)95KOfAK~hK-$9{BeIV_vF}ttgs(mIAm{vOcWhS3` zHSgMk3ae*rMwYu+Dwc?|GX@Wiv@Dc{4R2spiZ)gctPC6AEhorJey7*!nD6;;(e=jl zPOaCsayF3R4gih8QM3wvppD2|O67%evYBdjxjR=WJ!_PsXzNd`4k`6@YE_*y&f~#g zo;k7l)S>wv!iyb|>{fC^odA??$^osQiz^@KrSJnVzlZd^}UMvzqeyZHBnaN~h&0_n3YKRPv61A4E8v(5m@I zRQio;ck5NOou;R(^YGR9Bd=NJQuT)PM8O6MXn#w&2D_!)5No<;uy`wu=^nx0gB{X6 z-V2G=NOQIB@LXBxJG;ReZs|L_!EtWsJG;ReXQb}$1m;h??(YRx_h)h7u(-CfNL-=W zlxsNVn_a*_mBzcR8i%cMr>$3)w6`@vl~+C(K9hTsr0Y^uJJNd7DKaJ&nPWu}9U=4f z8xEU&F?Ig{Nu#w{t#qr2re~{fVkm?V;En(#X=;sSo%f6qAa(j^P@zW!GxUb_k_4dQ zgNhD0E5{{xtHnt~lw47AtbVQAj|D1)jt(0aoS%?hbE%MCbE%MCa;cC@K%(@lMh;-HY6w^5ZAdPcZOu z+L`w*T~js7>gU|N;a5K8=PKFvFFDrFxp~sIW3hSGzGJa@*0)z;^Q~U4#pg=BU5n0D zdb<;?&n+!6ubCXkP?+ncMkQ5v0N#w0qf>ego?uXjRO`K>FBnF% z&LsAt;&DFSInnJ>rB5Yo9484>XG8N;V2)LX|FDDX<$=m3`BL#fS&MNttooex! zoDp$2{{WH|>N%u9ocAs{*Jd?>g{l1&Qqo%D0NAV6aA+;eb|F>&07x7{rCH8@R_)sT zo>Zw)rAnVeDpaXbrAYKLrAn15RQefGq$yIRPqXQ>^xL^or02*R1g2RkI06T)`klyG zo^S-~gxW43oFg+D^4^7(sW2K*5MgNZ^$p$VG~e$Vxl^R)&CxI=Hpx-I5Itlp)eZoi zaq&%{;t9YE^UHc|mZ>pxq9Irh)Hipa(|^2g-TA6?ocX)3NldZSa1cFj*J2iGhXFd{ zu{Mi{Cjc`&+t9MqCId<$H7f!7mhSW#bJlOijhGFC~|_es4HG*gPPZx*0rWi z)mPR$>Rz&Q!n})$4vbWVe08R{b*kR4+6=@eDLE-QE5{`4Ro!za4Qt@FPC@HX&+{{5Q>EhS~#oXVG*rsTGqY?;&?)JeRy}S zIGu`{WJy;-x-wTrO5n*{87?d?C@vvYWy}$&)Sl@B?ABrvkOe`_LCtMN!A`XYHLo?V zJ_q7?t;Jqf)%AT}SI}`mWcV(`!8Ys8)RXzvxf@*-$Dfz5oCK diff --git a/app/design/frontend/Magento/rush/registration.php b/app/design/frontend/Magento/rush/registration.php deleted file mode 100644 index d7b767d71e06d..0000000000000 --- a/app/design/frontend/Magento/rush/registration.php +++ /dev/null @@ -1,13 +0,0 @@ - - - - Magento Rush - - media/preview.jpg - - diff --git a/app/design/frontend/Magento/rush/web/css/_styles-l.less b/app/design/frontend/Magento/rush/web/css/_styles-l.less deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/app/design/frontend/Magento/rush/web/css/_styles-m.less b/app/design/frontend/Magento/rush/web/css/_styles-m.less deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/app/design/frontend/Magento/rush/web/images/logo.svg b/app/design/frontend/Magento/rush/web/images/logo.svg deleted file mode 100644 index 556d839b8b84f..0000000000000 --- a/app/design/frontend/Magento/rush/web/images/logo.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/design/frontend/Magento/rush/web/js/foo.js b/app/design/frontend/Magento/rush/web/js/foo.js deleted file mode 100644 index c4ea9770085d2..0000000000000 --- a/app/design/frontend/Magento/rush/web/js/foo.js +++ /dev/null @@ -1 +0,0 @@ -console.log('logging from rush/web'); From 7412a33bf09aeb903a4f76cfd4294f591a25b7e8 Mon Sep 17 00:00:00 2001 From: Cristian Partica Date: Wed, 9 May 2018 12:24:37 -0500 Subject: [PATCH 168/236] MAGETWO-90375: Removing "Rush" theme work from 2.3.0 - removing rush theme from composer --- composer.json | 1 - composer.lock | 247 +++++++++++++++++++++++++------------------------- 2 files changed, 123 insertions(+), 125 deletions(-) diff --git a/composer.json b/composer.json index 856d078f8a340..e2a89ae1207a7 100644 --- a/composer.json +++ b/composer.json @@ -232,7 +232,6 @@ "magento/theme-adminhtml-backend": "*", "magento/theme-frontend-blank": "*", "magento/theme-frontend-luma": "*", - "magento/theme-frontend-rush": "*", "magento/language-de_de": "*", "magento/language-en_us": "*", "magento/language-es_es": "*", diff --git a/composer.lock b/composer.lock index ee7503ce57328..b104cf2929ba7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "b491d3c677cbffbbcb7a64da82bfbec5", - "content-hash": "7e60e718b5d717fa3cca1f7e588de9c4", + "content-hash": "daacd8800615d44aa1af0ac06c1ecc46", "packages": [ { "name": "braintree/braintree_php", @@ -52,7 +51,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2018-02-08 23:03:34" + "time": "2018-02-08T23:03:34+00:00" }, { "name": "colinmollenhour/cache-backend-file", @@ -85,7 +84,7 @@ ], "description": "The stock Zend_Cache_Backend_File backend has extremely poor performance for cleaning by tags making it become unusable as the number of cached items increases. This backend makes many changes resulting in a huge performance boost, especially for tag cleaning.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_File", - "time": "2018-04-05 15:28:43" + "time": "2018-04-05T15:28:43+00:00" }, { "name": "colinmollenhour/cache-backend-redis", @@ -121,7 +120,7 @@ ], "description": "Zend_Cache backend using Redis with full support for tags.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", - "time": "2017-10-05 20:50:44" + "time": "2017-10-05T20:50:44+00:00" }, { "name": "colinmollenhour/credis", @@ -161,7 +160,7 @@ ], "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", "homepage": "https://github.com/colinmollenhour/credis", - "time": "2017-10-05 20:28:58" + "time": "2017-10-05T20:28:58+00:00" }, { "name": "colinmollenhour/php-redis-session-abstract", @@ -198,7 +197,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2018-01-08 14:53:13" + "time": "2018-01-08T14:53:13+00:00" }, { "name": "composer/ca-bundle", @@ -254,7 +253,7 @@ "ssl", "tls" ], - "time": "2018-03-29 19:57:20" + "time": "2018-03-29T19:57:20+00:00" }, { "name": "composer/composer", @@ -331,7 +330,7 @@ "dependency", "package" ], - "time": "2018-04-13 10:04:24" + "time": "2018-04-13T10:04:24+00:00" }, { "name": "composer/semver", @@ -393,7 +392,7 @@ "validation", "versioning" ], - "time": "2016-08-30 16:08:34" + "time": "2016-08-30T16:08:34+00:00" }, { "name": "composer/spdx-licenses", @@ -454,7 +453,7 @@ "spdx", "validator" ], - "time": "2018-01-31 13:17:27" + "time": "2018-01-31T13:17:27+00:00" }, { "name": "container-interop/container-interop", @@ -485,7 +484,7 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", - "time": "2017-02-14 19:40:03" + "time": "2017-02-14T19:40:03+00:00" }, { "name": "elasticsearch/elasticsearch", @@ -540,7 +539,7 @@ "elasticsearch", "search" ], - "time": "2017-11-08 17:04:47" + "time": "2017-11-08T17:04:47+00:00" }, { "name": "guzzlehttp/ringphp", @@ -591,7 +590,7 @@ } ], "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "time": "2015-05-20 03:37:09" + "time": "2015-05-20T03:37:09+00:00" }, { "name": "guzzlehttp/streams", @@ -641,7 +640,7 @@ "Guzzle", "stream" ], - "time": "2014-10-12 19:18:40" + "time": "2014-10-12T19:18:40+00:00" }, { "name": "justinrainbow/json-schema", @@ -707,7 +706,7 @@ "json", "schema" ], - "time": "2018-02-14 22:26:30" + "time": "2018-02-14T22:26:30+00:00" }, { "name": "magento/composer", @@ -743,7 +742,7 @@ "AFL-3.0" ], "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2018-03-26 16:19:52" + "time": "2018-03-26T16:19:52+00:00" }, { "name": "magento/magento-composer-installer", @@ -822,7 +821,7 @@ "composer-installer", "magento" ], - "time": "2017-12-29 16:45:24" + "time": "2017-12-29T16:45:24+00:00" }, { "name": "magento/zendframework1", @@ -879,7 +878,7 @@ "source": "https://github.com/magento-engcom/zf1-php-7.2-support/tree/master", "issues": "https://github.com/magento-engcom/zf1-php-7.2-support/issues" }, - "time": "2018-04-06 17:12:22" + "time": "2018-04-06T17:12:22+00:00" }, { "name": "monolog/monolog", @@ -957,7 +956,7 @@ "logging", "psr-3" ], - "time": "2017-06-19 01:22:40" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "oyejorge/less.php", @@ -1019,7 +1018,7 @@ "php", "stylesheet" ], - "time": "2017-03-28 22:19:25" + "time": "2017-03-28T22:19:25+00:00" }, { "name": "paragonie/random_compat", @@ -1067,7 +1066,7 @@ "pseudorandom", "random" ], - "time": "2018-04-04 21:24:14" + "time": "2018-04-04T21:24:14+00:00" }, { "name": "pelago/emogrifier", @@ -1136,7 +1135,7 @@ "email", "pre-processing" ], - "time": "2018-01-05 23:30:21" + "time": "2018-01-05T23:30:21+00:00" }, { "name": "php-amqplib/php-amqplib", @@ -1207,7 +1206,7 @@ "queue", "rabbitmq" ], - "time": "2018-02-11 19:28:00" + "time": "2018-02-11T19:28:00+00:00" }, { "name": "phpseclib/mcrypt_compat", @@ -1256,7 +1255,7 @@ "encryption", "mcrypt" ], - "time": "2018-01-13 23:07:52" + "time": "2018-01-13T23:07:52+00:00" }, { "name": "phpseclib/phpseclib", @@ -1348,7 +1347,7 @@ "x.509", "x509" ], - "time": "2018-04-15 16:55:05" + "time": "2018-04-15T16:55:05+00:00" }, { "name": "psr/container", @@ -1397,7 +1396,7 @@ "container-interop", "psr" ], - "time": "2017-02-14 16:28:37" + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/http-message", @@ -1447,7 +1446,7 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", @@ -1494,7 +1493,7 @@ "psr", "psr-3" ], - "time": "2016-10-10 12:19:37" + "time": "2016-10-10T12:19:37+00:00" }, { "name": "ramsey/uuid", @@ -1574,7 +1573,7 @@ "identifier", "uuid" ], - "time": "2018-01-20 00:28:24" + "time": "2018-01-20T00:28:24+00:00" }, { "name": "react/promise", @@ -1620,7 +1619,7 @@ "promise", "promises" ], - "time": "2017-03-25 12:08:31" + "time": "2017-03-25T12:08:31+00:00" }, { "name": "seld/cli-prompt", @@ -1668,7 +1667,7 @@ "input", "prompt" ], - "time": "2017-03-18 11:32:45" + "time": "2017-03-18T11:32:45+00:00" }, { "name": "seld/jsonlint", @@ -1717,7 +1716,7 @@ "parser", "validator" ], - "time": "2018-01-24 12:46:19" + "time": "2018-01-24T12:46:19+00:00" }, { "name": "seld/phar-utils", @@ -1761,7 +1760,7 @@ "keywords": [ "phra" ], - "time": "2015-10-13 18:44:15" + "time": "2015-10-13T18:44:15+00:00" }, { "name": "symfony/console", @@ -1829,7 +1828,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-04-03 05:24:00" + "time": "2018-04-03T05:24:00+00:00" }, { "name": "symfony/event-dispatcher", @@ -1892,7 +1891,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06 07:35:43" + "time": "2018-04-06T07:35:43+00:00" }, { "name": "symfony/filesystem", @@ -1941,7 +1940,7 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-02-22 10:50:29" + "time": "2018-02-22T10:50:29+00:00" }, { "name": "symfony/finder", @@ -1990,7 +1989,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-04-04 05:10:37" + "time": "2018-04-04T05:10:37+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2049,7 +2048,7 @@ "portable", "shim" ], - "time": "2018-01-30 19:27:44" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/process", @@ -2098,7 +2097,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-04-03 05:24:00" + "time": "2018-04-03T05:24:00+00:00" }, { "name": "tedivm/jshrink", @@ -2144,7 +2143,7 @@ "javascript", "minifier" ], - "time": "2017-12-08 00:59:56" + "time": "2017-12-08T00:59:56+00:00" }, { "name": "tubalmartin/cssmin", @@ -2197,7 +2196,7 @@ "minify", "yui" ], - "time": "2018-01-15 15:26:51" + "time": "2018-01-15T15:26:51+00:00" }, { "name": "webonyx/graphql-php", @@ -2244,7 +2243,7 @@ "api", "graphql" ], - "time": "2017-12-12 09:03:21" + "time": "2017-12-12T09:03:21+00:00" }, { "name": "zendframework/zend-captcha", @@ -2301,7 +2300,7 @@ "captcha", "zf2" ], - "time": "2017-02-23 08:09:44" + "time": "2017-02-23T08:09:44+00:00" }, { "name": "zendframework/zend-code", @@ -2354,7 +2353,7 @@ "code", "zf2" ], - "time": "2017-10-20 15:21:32" + "time": "2017-10-20T15:21:32+00:00" }, { "name": "zendframework/zend-config", @@ -2410,7 +2409,7 @@ "config", "zf2" ], - "time": "2016-02-04 23:01:10" + "time": "2016-02-04T23:01:10+00:00" }, { "name": "zendframework/zend-console", @@ -2463,7 +2462,7 @@ "console", "zf" ], - "time": "2018-01-25 19:08:04" + "time": "2018-01-25T19:08:04+00:00" }, { "name": "zendframework/zend-crypt", @@ -2513,7 +2512,7 @@ "crypt", "zf2" ], - "time": "2016-02-03 23:46:30" + "time": "2016-02-03T23:46:30+00:00" }, { "name": "zendframework/zend-db", @@ -2571,7 +2570,7 @@ "db", "zf" ], - "time": "2018-04-09 13:21:36" + "time": "2018-04-09T13:21:36+00:00" }, { "name": "zendframework/zend-di", @@ -2618,7 +2617,7 @@ "di", "zf2" ], - "time": "2016-04-25 20:58:11" + "time": "2016-04-25T20:58:11+00:00" }, { "name": "zendframework/zend-diactoros", @@ -2670,7 +2669,7 @@ "psr", "psr-7" ], - "time": "2018-02-26 15:44:50" + "time": "2018-02-26T15:44:50+00:00" }, { "name": "zendframework/zend-escaper", @@ -2714,7 +2713,7 @@ "escaper", "zf2" ], - "time": "2016-06-30 19:48:38" + "time": "2016-06-30T19:48:38+00:00" }, { "name": "zendframework/zend-eventmanager", @@ -2761,7 +2760,7 @@ "eventmanager", "zf2" ], - "time": "2017-12-12 17:48:56" + "time": "2017-12-12T17:48:56+00:00" }, { "name": "zendframework/zend-feed", @@ -2822,7 +2821,7 @@ "feed", "zf" ], - "time": "2017-12-04 17:59:38" + "time": "2017-12-04T17:59:38+00:00" }, { "name": "zendframework/zend-filter", @@ -2885,7 +2884,7 @@ "filter", "zf" ], - "time": "2018-04-11 16:20:04" + "time": "2018-04-11T16:20:04+00:00" }, { "name": "zendframework/zend-form", @@ -2963,7 +2962,7 @@ "form", "zf" ], - "time": "2017-12-06 21:09:08" + "time": "2017-12-06T21:09:08+00:00" }, { "name": "zendframework/zend-http", @@ -3016,7 +3015,7 @@ "zend", "zf" ], - "time": "2017-10-13 12:06:24" + "time": "2017-10-13T12:06:24+00:00" }, { "name": "zendframework/zend-hydrator", @@ -3074,7 +3073,7 @@ "hydrator", "zf2" ], - "time": "2016-02-18 22:38:26" + "time": "2016-02-18T22:38:26+00:00" }, { "name": "zendframework/zend-i18n", @@ -3141,7 +3140,7 @@ "i18n", "zf2" ], - "time": "2017-05-17 17:00:12" + "time": "2017-05-17T17:00:12+00:00" }, { "name": "zendframework/zend-inputfilter", @@ -3194,7 +3193,7 @@ "inputfilter", "zf" ], - "time": "2018-01-22 19:41:18" + "time": "2018-01-22T19:41:18+00:00" }, { "name": "zendframework/zend-json", @@ -3249,7 +3248,7 @@ "json", "zf2" ], - "time": "2016-02-04 21:20:26" + "time": "2016-02-04T21:20:26+00:00" }, { "name": "zendframework/zend-loader", @@ -3293,7 +3292,7 @@ "loader", "zf2" ], - "time": "2015-06-03 14:05:47" + "time": "2015-06-03T14:05:47+00:00" }, { "name": "zendframework/zend-log", @@ -3364,7 +3363,7 @@ "logging", "zf2" ], - "time": "2018-04-09 21:59:51" + "time": "2018-04-09T21:59:51+00:00" }, { "name": "zendframework/zend-mail", @@ -3426,7 +3425,7 @@ "mail", "zf2" ], - "time": "2018-03-01 18:57:00" + "time": "2018-03-01T18:57:00+00:00" }, { "name": "zendframework/zend-math", @@ -3476,7 +3475,7 @@ "math", "zf2" ], - "time": "2016-04-07 16:29:53" + "time": "2016-04-07T16:29:53+00:00" }, { "name": "zendframework/zend-mime", @@ -3527,7 +3526,7 @@ "mime", "zf" ], - "time": "2017-11-28 15:02:22" + "time": "2017-11-28T15:02:22+00:00" }, { "name": "zendframework/zend-modulemanager", @@ -3587,7 +3586,7 @@ "modulemanager", "zf" ], - "time": "2017-12-02 06:11:18" + "time": "2017-12-02T06:11:18+00:00" }, { "name": "zendframework/zend-mvc", @@ -3679,7 +3678,7 @@ "mvc", "zf2" ], - "time": "2017-12-14 22:44:10" + "time": "2017-12-14T22:44:10+00:00" }, { "name": "zendframework/zend-psr7bridge", @@ -3728,7 +3727,7 @@ "psr", "psr-7" ], - "time": "2016-05-10 21:44:39" + "time": "2016-05-10T21:44:39+00:00" }, { "name": "zendframework/zend-serializer", @@ -3786,7 +3785,7 @@ "serializer", "zf2" ], - "time": "2017-11-20 22:21:04" + "time": "2017-11-20T22:21:04+00:00" }, { "name": "zendframework/zend-server", @@ -3832,7 +3831,7 @@ "server", "zf2" ], - "time": "2016-06-20 22:27:55" + "time": "2016-06-20T22:27:55+00:00" }, { "name": "zendframework/zend-servicemanager", @@ -3884,7 +3883,7 @@ "servicemanager", "zf2" ], - "time": "2017-12-05 16:27:36" + "time": "2017-12-05T16:27:36+00:00" }, { "name": "zendframework/zend-session", @@ -3951,7 +3950,7 @@ "session", "zf" ], - "time": "2018-02-22 16:33:54" + "time": "2018-02-22T16:33:54+00:00" }, { "name": "zendframework/zend-soap", @@ -4004,7 +4003,7 @@ "soap", "zf2" ], - "time": "2018-01-29 17:51:26" + "time": "2018-01-29T17:51:26+00:00" }, { "name": "zendframework/zend-stdlib", @@ -4063,7 +4062,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12 21:17:31" + "time": "2016-04-12T21:17:31+00:00" }, { "name": "zendframework/zend-text", @@ -4110,7 +4109,7 @@ "text", "zf2" ], - "time": "2016-02-08 19:03:52" + "time": "2016-02-08T19:03:52+00:00" }, { "name": "zendframework/zend-uri", @@ -4157,7 +4156,7 @@ "uri", "zf2" ], - "time": "2018-04-10 17:08:10" + "time": "2018-04-10T17:08:10+00:00" }, { "name": "zendframework/zend-validator", @@ -4228,7 +4227,7 @@ "validator", "zf2" ], - "time": "2018-02-01 17:05:33" + "time": "2018-02-01T17:05:33+00:00" }, { "name": "zendframework/zend-view", @@ -4315,7 +4314,7 @@ "view", "zf2" ], - "time": "2018-01-17 22:21:50" + "time": "2018-01-17T22:21:50+00:00" } ], "packages-dev": [ @@ -4385,7 +4384,7 @@ "docblock", "parser" ], - "time": "2017-12-06 07:11:42" + "time": "2017-12-06T07:11:42+00:00" }, { "name": "doctrine/instantiator", @@ -4439,7 +4438,7 @@ "constructor", "instantiate" ], - "time": "2017-07-22 11:58:36" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "doctrine/lexer", @@ -4493,7 +4492,7 @@ "lexer", "parser" ], - "time": "2014-09-09 13:34:57" + "time": "2014-09-09T13:34:57+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -4580,7 +4579,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-03-20 18:07:08" + "time": "2018-03-20T18:07:08+00:00" }, { "name": "lusitanian/oauth", @@ -4647,7 +4646,7 @@ "oauth", "security" ], - "time": "2016-07-12 22:15:40" + "time": "2016-07-12T22:15:40+00:00" }, { "name": "myclabs/deep-copy", @@ -4692,7 +4691,7 @@ "object", "object graph" ], - "time": "2017-10-19 19:58:43" + "time": "2017-10-19T19:58:43+00:00" }, { "name": "pdepend/pdepend", @@ -4732,7 +4731,7 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-12-13 13:21:38" + "time": "2017-12-13T13:21:38+00:00" }, { "name": "phar-io/manifest", @@ -4787,7 +4786,7 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05 18:14:27" + "time": "2017-03-05T18:14:27+00:00" }, { "name": "phar-io/version", @@ -4834,7 +4833,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05 17:38:23" + "time": "2017-03-05T17:38:23+00:00" }, { "name": "php-cs-fixer/diff", @@ -4885,7 +4884,7 @@ "keywords": [ "diff" ], - "time": "2018-02-15 16:58:55" + "time": "2018-02-15T16:58:55+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -4939,7 +4938,7 @@ "reflection", "static analysis" ], - "time": "2017-09-11 18:02:19" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -4990,7 +4989,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30 07:14:17" + "time": "2017-11-30T07:14:17+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -5037,7 +5036,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14 14:27:02" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpmd/phpmd", @@ -5103,7 +5102,7 @@ "phpmd", "pmd" ], - "time": "2017-01-20 14:41:10" + "time": "2017-01-20T14:41:10+00:00" }, { "name": "phpspec/prophecy", @@ -5166,7 +5165,7 @@ "spy", "stub" ], - "time": "2018-02-19 10:16:54" + "time": "2018-02-19T10:16:54+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5229,7 +5228,7 @@ "testing", "xunit" ], - "time": "2018-04-06 15:36:58" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5276,7 +5275,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27 13:52:08" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -5317,7 +5316,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -5366,7 +5365,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26 11:10:40" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", @@ -5415,7 +5414,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27 05:48:46" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", @@ -5499,7 +5498,7 @@ "testing", "xunit" ], - "time": "2017-08-03 13:59:28" + "time": "2017-08-03T13:59:28+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -5558,7 +5557,7 @@ "mock", "xunit" ], - "time": "2017-08-03 14:08:16" + "time": "2017-08-03T14:08:16+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5603,7 +5602,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04 06:30:41" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", @@ -5667,7 +5666,7 @@ "compare", "equality" ], - "time": "2017-03-03 06:26:08" + "time": "2017-03-03T06:26:08+00:00" }, { "name": "sebastian/diff", @@ -5719,7 +5718,7 @@ "keywords": [ "diff" ], - "time": "2017-05-22 07:24:03" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -5769,7 +5768,7 @@ "environment", "hhvm" ], - "time": "2017-07-01 08:51:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", @@ -5836,7 +5835,7 @@ "export", "exporter" ], - "time": "2017-04-03 13:19:02" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/finder-facade", @@ -5875,7 +5874,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2017-11-18 17:31:49" + "time": "2017-11-18T17:31:49+00:00" }, { "name": "sebastian/global-state", @@ -5926,7 +5925,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27 15:39:26" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", @@ -5973,7 +5972,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03 12:35:26" + "time": "2017-08-03T12:35:26+00:00" }, { "name": "sebastian/object-reflector", @@ -6018,7 +6017,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29 09:07:27" + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/phpcpd", @@ -6068,7 +6067,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2017-11-16 08:49:28" + "time": "2017-11-16T08:49:28+00:00" }, { "name": "sebastian/recursion-context", @@ -6121,7 +6120,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03 06:23:57" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -6163,7 +6162,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", @@ -6206,7 +6205,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03 07:35:21" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -6257,7 +6256,7 @@ "phpcs", "standards" ], - "time": "2017-12-19 21:44:46" + "time": "2017-12-19T21:44:46+00:00" }, { "name": "symfony/config", @@ -6319,7 +6318,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-03-19 22:35:49" + "time": "2018-03-19T22:35:49+00:00" }, { "name": "symfony/dependency-injection", @@ -6390,7 +6389,7 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-04-02 09:52:41" + "time": "2018-04-02T09:52:41+00:00" }, { "name": "symfony/options-resolver", @@ -6444,7 +6443,7 @@ "configuration", "options" ], - "time": "2018-01-18 22:19:33" + "time": "2018-01-18T22:19:33+00:00" }, { "name": "symfony/polyfill-php70", @@ -6503,7 +6502,7 @@ "portable", "shim" ], - "time": "2018-01-30 19:27:44" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/polyfill-php72", @@ -6558,7 +6557,7 @@ "portable", "shim" ], - "time": "2018-01-31 17:43:24" + "time": "2018-01-31T17:43:24+00:00" }, { "name": "symfony/stopwatch", @@ -6607,7 +6606,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19 16:50:22" + "time": "2018-02-19T16:50:22+00:00" }, { "name": "theseer/fdomdocument", @@ -6647,7 +6646,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-06-30 11:53:12" + "time": "2017-06-30T11:53:12+00:00" }, { "name": "theseer/tokenizer", @@ -6687,7 +6686,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07 12:08:54" + "time": "2017-04-07T12:08:54+00:00" }, { "name": "webmozart/assert", @@ -6737,7 +6736,7 @@ "check", "validate" ], - "time": "2018-01-29 19:49:41" + "time": "2018-01-29T19:49:41+00:00" } ], "aliases": [], From a8e80445f26c523cb1361e8d87fdedc7b1edbdfd Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 9 May 2018 13:16:53 -0500 Subject: [PATCH 169/236] MAGETWO-90182: Generate benchmark --- setup/performance-toolkit/benchmark.jmx | 26 ++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 74144cd7a3a82..2155f774b1e9a 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -40720,7 +40720,7 @@ if (totalCount == null) { false - {"query":"{\n categories(id: 4) {\n category {\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} + {"query":"{\n category(id: 1) {\n name\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null} = @@ -40743,12 +40743,32 @@ if (totalCount == null) { - graphql_category_query_category_tree - $.data.categories.category_tree + graphql_category_query_name + $.data.category.name BODY + + + String name = vars.get("graphql_category_query_name"); +if (name == null) { + Failure = true; + FailureMessage = "Not Expected \"children\" to be null"; +} else { + if (!name.equals("Root Catalog")) { + Failure = true; + FailureMessage = "Expected \"name\" to equal \"Root Catalog\", Actual: " + name; + } else { + Failure = false; + } +} + + + + false + + From 2f09cd7087d5fe6fde60051e7d9965d18386c29b Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 9 May 2018 13:26:30 -0500 Subject: [PATCH 170/236] MAGETWO-90182: Remove broken test --- .../Magento/GraphQl/Catalog/CategoryTest.php | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index b8c8a6aa3f988..eb138d738ea10 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -22,72 +22,6 @@ protected function setUp() $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } - /** - * @magentoApiDataFixture Magento/Catalog/_files/categories_no_products.php - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @param int $categoryId - * @param array $expectedResponse - * - * @dataProvider categorySubtreeTestDataProvider - */ - public function testCategoriesSubtree($categoryId, $expectedResponse) - { - $query = <<objectManager->create( - \Magento\Integration\Api\CustomerTokenServiceInterface::class - ); - $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); - - $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - $response = $this->graphQlQuery($query, [], '', $headerMap); - $this->assertEquals($expectedResponse, $response); - } - - public function categorySubtreeTestDataProvider() - { - return [ - [ - 'category_id' => 6, - 'expected_subtree' => [ - 'category' => [ - 'id' => 6, - 'level' => 2, - 'name' => 'Category 2', - 'path' => '1/2/6', - 'product_count' => 0, - 'children' => [] - ], - ] - ] - ]; - } - /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Catalog/_files/categories.php From 8782b34dbe29fe9fb0c1479ab97f89c4f52f3e88 Mon Sep 17 00:00:00 2001 From: Stas Kozar Date: Thu, 10 May 2018 11:13:32 +0300 Subject: [PATCH 171/236] MAGETWO-90148: Overlapping reindex processes of catalogrule_rule --- .../Model/Indexer/IndexBuilder.php | 22 ++- .../Model/Indexer/IndexerTableSwapper.php | 124 +++++++++++++ .../Indexer/IndexerTableSwapperInterface.php | 33 ++++ .../Model/Indexer/ReindexRuleGroupWebsite.php | 23 ++- .../Model/Indexer/ReindexRuleProduct.php | 21 ++- .../Indexer/RuleProductPricesPersistor.php | 21 ++- .../Indexer/RuleProductsSelectBuilder.php | 21 ++- .../Model/Indexer/IndexerTableSwapperTest.php | 174 ++++++++++++++++++ .../Indexer/ReindexRuleGroupWebsiteTest.php | 58 +++--- .../Model/Indexer/ReindexRuleProductTest.php | 27 ++- .../RuleProductPricesPersistorTest.php | 31 +++- .../Indexer/RuleProductsSelectBuilderTest.php | 22 ++- app/code/Magento/CatalogRule/etc/di.xml | 1 + app/code/Magento/Indexer/Model/Indexer.php | 9 +- 14 files changed, 503 insertions(+), 84 deletions(-) create mode 100644 app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php create mode 100644 app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php create mode 100644 app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php index f2dd8968a903d..1f62200fc6b1b 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php @@ -12,6 +12,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\CatalogRule\Model\Indexer\IndexBuilder\ProductLoader; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; /** * @api @@ -136,6 +137,11 @@ class IndexBuilder */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @var ProductLoader */ @@ -160,6 +166,7 @@ class IndexBuilder * @param RuleProductPricesPersistor|null $pricesPersistor * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher * @param ProductLoader|null $productLoader + * @param TableSwapper|null $tableSwapper * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -180,7 +187,8 @@ public function __construct( ReindexRuleProductPrice $reindexRuleProductPrice = null, RuleProductPricesPersistor $pricesPersistor = null, \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null, - ProductLoader $productLoader = null + ProductLoader $productLoader = null, + TableSwapper $tableSwapper = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -218,6 +226,8 @@ public function __construct( $this->productLoader = $productLoader ?? ObjectManager::getInstance()->get( ProductLoader::class ); + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -296,13 +306,6 @@ public function reindexFull() */ protected function doReindexFull() { - $this->connection->truncateTable( - $this->getTable($this->activeTableSwitcher->getAdditionalTableName('catalogrule_product')) - ); - $this->connection->truncateTable( - $this->getTable($this->activeTableSwitcher->getAdditionalTableName('catalogrule_product_price')) - ); - foreach ($this->getAllRules() as $rule) { $this->reindexRuleProduct->execute($rule, $this->batchCount, true); } @@ -310,8 +313,7 @@ protected function doReindexFull() $this->reindexRuleProductPrice->execute($this->batchCount, null, true); $this->reindexRuleGroupWebsite->execute(true); - $this->activeTableSwitcher->switchTable( - $this->connection, + $this->tableSwapper->swapIndexTables( [ $this->getTable('catalogrule_product'), $this->getTable('catalogrule_product_price'), diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php new file mode 100644 index 0000000000000..44c6a28a5fbe2 --- /dev/null +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php @@ -0,0 +1,124 @@ +resourceConnection = $resource; + } + + /** + * Create temporary table based on given table to use instead of original. + * + * @param string $originalTableName + * + * @return string Created table name. + */ + private function createTemporaryTable(string $originalTableName): string + { + $temporaryTableName = $this->resourceConnection->getTableName( + $originalTableName . '__temp' . $this->generateRandomSuffix() + ); + + $this->resourceConnection->getConnection()->createTable( + $this->resourceConnection->getConnection()->createTableByDdl( + $this->resourceConnection->getTableName($originalTableName), + $temporaryTableName + ) + ); + + return $temporaryTableName; + } + + /** + * Random suffix for temporary tables not to conflict with each other. + * + * @return string + */ + private function generateRandomSuffix(): string + { + return bin2hex(random_bytes(4)); + } + + /** + * @inheritDoc + */ + public function getWorkingTableName(string $originalTable): string + { + $originalTable = $this->resourceConnection->getTableName($originalTable); + if (!array_key_exists($originalTable, $this->temporaryTables)) { + $this->temporaryTables[$originalTable] = $this->createTemporaryTable($originalTable); + } + + return $this->temporaryTables[$originalTable]; + } + + /** + * @inheritDoc + */ + public function swapIndexTables(array $originalTablesNames) + { + $toRename = []; + /** @var string[] $toDrop */ + $toDrop = []; + /** @var string[] $temporaryTablesRenamed */ + $temporaryTablesRenamed = []; + //Renaming temporary tables to original tables' names, dropping old + //tables. + foreach ($originalTablesNames as $tableName) { + $tableName = $this->resourceConnection->getTableName($tableName); + $temporaryOriginalName = $this->resourceConnection->getTableName( + $tableName . $this->generateRandomSuffix() + ); + $temporaryTableName = $this->getWorkingTableName($tableName); + $toRename[] = [ + 'oldName' => $tableName, + 'newName' => $temporaryOriginalName, + ]; + $toRename[] = [ + 'oldName' => $temporaryTableName, + 'newName' => $tableName, + ]; + $toDrop[] = $temporaryOriginalName; + $temporaryTablesRenamed[] = $tableName; + } + + //Swapping tables. + $this->resourceConnection->getConnection()->renameTablesBatch($toRename); + //Cleaning up. + foreach ($temporaryTablesRenamed as $tableName) { + unset($this->temporaryTables[$tableName]); + } + //Removing old ones. + foreach ($toDrop as $tableName) { + $this->resourceConnection->getConnection()->dropTable($tableName); + } + } +} diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php new file mode 100644 index 0000000000000..2f37e680949ae --- /dev/null +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapperInterface.php @@ -0,0 +1,33 @@ +dateTime = $dateTime; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -61,10 +74,10 @@ public function execute($useAdditionalTable = false) $ruleProductTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_group_website') + $this->tableSwapper->getWorkingTableName('catalogrule_group_website') ); $ruleProductTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 534061d593123..55a234bb8ae27 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Reindex rule relations with products. */ @@ -17,20 +21,29 @@ class ReindexRuleProduct private $resource; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -65,7 +78,7 @@ public function execute( $indexTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php index 853be1888b5b9..0b1264a216257 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductPricesPersistor.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Persist product prices to index table. */ @@ -22,23 +26,32 @@ class RuleProductPricesPersistor private $dateFormat; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\Stdlib\DateTime $dateFormat * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\Stdlib\DateTime $dateFormat, \Magento\Framework\App\ResourceConnection $resource, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->dateFormat = $dateFormat; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -59,7 +72,7 @@ public function execute(array $priceData, $useAdditionalTable = false) $indexTable = $this->resource->getTableName('catalogrule_product_price'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product_price') + $this->tableSwapper->getWorkingTableName('catalogrule_product_price') ); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php index 25d164aeee5c3..6989a33535ad8 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php @@ -6,6 +6,10 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\App\ObjectManager; + /** * Build select for rule relation with product. */ @@ -32,29 +36,38 @@ class RuleProductsSelectBuilder private $metadataPool; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher + * @var ActiveTableSwitcher */ private $activeTableSwitcher; + /** + * @var TableSwapper + */ + private $tableSwapper; + /** * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool - * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + * @param ActiveTableSwitcher $activeTableSwitcher + * @param TableSwapper|null $tableSwapper */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, \Magento\Eav\Model\Config $eavConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\EntityManager\MetadataPool $metadataPool, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher + ActiveTableSwitcher $activeTableSwitcher, + TableSwapper $tableSwapper = null ) { $this->eavConfig = $eavConfig; $this->storeManager = $storeManager; $this->metadataPool = $metadataPool; $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** @@ -74,7 +87,7 @@ public function build( $indexTable = $this->resource->getTableName('catalogrule_product'); if ($useAdditionalTable) { $indexTable = $this->resource->getTableName( - $this->activeTableSwitcher->getAdditionalTableName('catalogrule_product') + $this->tableSwapper->getWorkingTableName('catalogrule_product') ); } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php new file mode 100644 index 0000000000000..747621725b405 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexerTableSwapperTest.php @@ -0,0 +1,174 @@ +resourceConnectionMock = $this->createMock(ResourceConnection::class); + + $this->adapterInterfaceMock = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass(); + /** @var \Zend_Db_Statement_Interface $statementInterfaceMock */ + $this->statementInterfaceMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class) + ->getMockForAbstractClass(); + /** @var Table $tableMock */ + $this->tableMock = $this->createMock(Table::class); + $this->resourceConnectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($this->adapterInterfaceMock); + } + + /** + * @return void + */ + public function testGetWorkingTableNameWithExistingTemporaryTable(): void + { + $model = new IndexerTableSwapper($this->resourceConnectionMock); + $originalTableName = 'catalogrule_product'; + $temporaryTableNames = ['catalogrule_product' => 'catalogrule_product__temp9604']; + $this->setObjectProperty($model, 'temporaryTables', $temporaryTableNames); + + $this->resourceConnectionMock->expects($this->once()) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + + $this->assertEquals( + $temporaryTableNames[$originalTableName], + $model->getWorkingTableName($originalTableName) + ); + } + + /** + * @return void + */ + public function testGetWorkingTableNameWithoutExistingTemporaryTable(): void + { + $model = new IndexerTableSwapper($this->resourceConnectionMock); + $originalTableName = 'catalogrule_product'; + $temporaryTableName = 'catalogrule_product__temp9604'; + $this->setObjectProperty($model, 'temporaryTables', []); + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getTableName') + ->with($this->stringStartsWith($originalTableName . '__temp')) + ->willReturn($temporaryTableName); + $this->adapterInterfaceMock->expects($this->once()) + ->method('createTable') + ->willReturn($this->statementInterfaceMock); + $this->adapterInterfaceMock->expects($this->once()) + ->method('createTableByDdl') + ->willReturn($this->tableMock); + + $this->assertEquals( + $temporaryTableName, + $model->getWorkingTableName($originalTableName) + ); + } + + /** + * Sets object non-public property. + * + * @param mixed $object + * @param string $propertyName + * @param mixed $value + * + * @return void + */ + private function setObjectProperty($object, string $propertyName, $value): void + { + $reflectionClass = new \ReflectionClass($object); + $reflectionProperty = $reflectionClass->getProperty($propertyName); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + } + + /** + * @return void + */ + public function testSwapIndexTables(): void + { + $model = $this->getMockBuilder(IndexerTableSwapper::class) + ->setMethods(['getWorkingTableName']) + ->setConstructorArgs([$this->resourceConnectionMock]) + ->getMock(); + $originalTableName = 'catalogrule_product'; + $temporaryOriginalTableName = 'catalogrule_product9604'; + $temporaryTableName = 'catalogrule_product__temp9604'; + $toRename = [ + [ + 'oldName' => $originalTableName, + 'newName' => $temporaryOriginalTableName, + ], + [ + 'oldName' => $temporaryTableName, + 'newName' => $originalTableName, + ], + ]; + + $this->resourceConnectionMock->expects($this->at(0)) + ->method('getTableName') + ->with($originalTableName) + ->willReturn($originalTableName); + $this->resourceConnectionMock->expects($this->at(1)) + ->method('getTableName') + ->with($this->stringStartsWith($originalTableName)) + ->willReturn($temporaryOriginalTableName); + $model->expects($this->once()) + ->method('getWorkingTableName') + ->with($originalTableName) + ->willReturn($temporaryTableName); + $this->adapterInterfaceMock->expects($this->once()) + ->method('renameTablesBatch') + ->with($toRename) + ->willReturn(true); + $this->adapterInterfaceMock->expects($this->once()) + ->method('dropTable') + ->with($temporaryOriginalTableName) + ->willReturn(true); + + $model->swapIndexTables([$originalTableName]); + } +} diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php index d60a662193e54..f02c2c643f809 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleGroupWebsiteTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class ReindexRuleGroupWebsiteTest extends \PHPUnit\Framework\TestCase { /** @@ -24,10 +27,15 @@ class ReindexRuleGroupWebsiteTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) @@ -37,13 +45,17 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) + $this->getMockBuilder(ActiveTableSwitcher::class) ->disableOriginalConstructor() ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleGroupWebsite( $this->dateTimeMock, $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -55,31 +67,25 @@ public function testExecute() $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); $this->dateTimeMock->expects($this->once())->method('gmtTimestamp')->willReturn($timeStamp); - $this->activeTableSwitcherMock->expects($this->at(0)) - ->method('getAdditionalTableName') - ->with('catalogrule_group_website') - ->willReturn('catalogrule_group_website_replica'); - $this->activeTableSwitcherMock->expects($this->at(1)) - ->method('getAdditionalTableName') - ->with('catalogrule_product') - ->willReturn('catalogrule_product_replica'); + $this->tableSwapperMock->expects($this->any()) + ->method('getWorkingTableName') + ->willReturnMap( + [ + ['catalogrule_group_website', 'catalogrule_group_website_replica'], + ['catalogrule_product', 'catalogrule_product_replica'], + ] + ); - $this->resourceMock->expects($this->at(1)) - ->method('getTableName') - ->with('catalogrule_group_website') - ->willReturn('catalogrule_group_website'); - $this->resourceMock->expects($this->at(2)) - ->method('getTableName') - ->with('catalogrule_product') - ->willReturn('catalogrule_product'); - $this->resourceMock->expects($this->at(3)) - ->method('getTableName') - ->with('catalogrule_group_website_replica') - ->willReturn('catalogrule_group_website_replica'); - $this->resourceMock->expects($this->at(4)) + $this->resourceMock->expects($this->any()) ->method('getTableName') - ->with('catalogrule_product_replica') - ->willReturn('catalogrule_product_replica'); + ->willReturnMap( + [ + ['catalogrule_group_website', 'default', 'catalogrule_group_website'], + ['catalogrule_product', 'default', 'catalogrule_product'], + ['catalogrule_group_website_replica', 'default', 'catalogrule_group_website_replica'], + ['catalogrule_product_replica', 'default', 'catalogrule_product_replica'], + ] + ); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index b829468396bf0..0dbbaee8d2871 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase { /** @@ -19,22 +22,30 @@ class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) + ->disableOriginalConstructor() + ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct( $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -71,8 +82,8 @@ public function testExecute() $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn(1); $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product') ->willReturn('catalogrule_product_replica'); diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php index 3efe26971627e..03163aa2d7c45 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductPricesPersistorTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; + class RuleProductPricesPersistorTest extends \PHPUnit\Framework\TestCase { /** @@ -24,10 +27,15 @@ class RuleProductPricesPersistorTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class) @@ -36,14 +44,17 @@ protected function setUp() $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) + ->disableOriginalConstructor() + ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor( $this->dateTimeMock, $this->resourceMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -64,8 +75,8 @@ public function testExecute() ]; $tableName = 'catalogrule_product_price_replica'; - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product_price') ->willReturn($tableName); @@ -120,8 +131,8 @@ public function testExecuteWithException() ]; $tableName = 'catalogrule_product_price_replica'; - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with('catalogrule_product_price') ->willReturn($tableName); diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php index 92b4bb353f046..e43fe41dc2127 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/RuleProductsSelectBuilderTest.php @@ -7,6 +7,8 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; @@ -35,7 +37,7 @@ class RuleProductsSelectBuilderTest extends \PHPUnit\Framework\TestCase private $resourceMock; /** - * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject */ private $activeTableSwitcherMock; @@ -49,6 +51,11 @@ class RuleProductsSelectBuilderTest extends \PHPUnit\Framework\TestCase */ private $metadataPoolMock; + /** + * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableSwapperMock; + protected function setUp() { $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) @@ -56,8 +63,7 @@ protected function setUp() $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->activeTableSwitcherMock = - $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class) + $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) ->disableOriginalConstructor() ->getMock(); $this->eavConfigMock = $this->getMockBuilder(\Magento\Eav\Model\Config::class) @@ -66,13 +72,17 @@ protected function setUp() $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) ->disableOriginalConstructor() ->getMock(); + $this->tableSwapperMock = $this->getMockForAbstractClass( + IndexerTableSwapperInterface::class + ); $this->model = new \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder( $this->resourceMock, $this->eavConfigMock, $this->storeManagerMock, $this->metadataPoolMock, - $this->activeTableSwitcherMock + $this->activeTableSwitcherMock, + $this->tableSwapperMock ); } @@ -92,8 +102,8 @@ public function testBuild() $connectionMock = $this->getMockBuilder(AdapterInterface::class)->disableOriginalConstructor()->getMock(); $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); - $this->activeTableSwitcherMock->expects($this->once()) - ->method('getAdditionalTableName') + $this->tableSwapperMock->expects($this->once()) + ->method('getWorkingTableName') ->with($ruleTable) ->willReturn($rplTable); diff --git a/app/code/Magento/CatalogRule/etc/di.xml b/app/code/Magento/CatalogRule/etc/di.xml index e29f5541fd77c..40893592c3d0f 100644 --- a/app/code/Magento/CatalogRule/etc/di.xml +++ b/app/code/Magento/CatalogRule/etc/di.xml @@ -149,4 +149,5 @@ CatalogRuleCustomConditionProvider + diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php index 58b2e9ed76a78..87a7cce58e1a5 100644 --- a/app/code/Magento/Indexer/Model/Indexer.php +++ b/app/code/Magento/Indexer/Model/Indexer.php @@ -398,7 +398,7 @@ protected function getStructureInstance() * Regenerate full index * * @return void - * @throws \Exception + * @throws \Throwable */ public function reindexAll() { @@ -414,16 +414,11 @@ public function reindexAll() $state->setStatus(StateInterface::STATUS_VALID); $state->save(); $this->getView()->resume(); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { $state->setStatus(StateInterface::STATUS_INVALID); $state->save(); $this->getView()->resume(); throw $exception; - } catch (\Error $error) { - $state->setStatus(StateInterface::STATUS_INVALID); - $state->save(); - $this->getView()->resume(); - throw $error; } } } From 3195d70e3db2466653dc8eaf7a5b00603652a2ef Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Thu, 10 May 2018 11:34:17 +0300 Subject: [PATCH 172/236] MAGETWO-90329: Cart Price Rules - Category Drill Down problems --- .../view/adminhtml/requirejs-config.js | 13 +- .../catalog/category/checkboxes/tree.phtml | 198 ++----------- .../web/js/category-checkbox-tree.js | 277 ++++++++++++++++++ .../Magento/Rule/view/adminhtml/web/rules.js | 2 +- .../Adminhtml/Promo/Widget/CategoriesJson.php | 26 ++ .../css/source/components/_rules-temp.less | 5 + .../SalesRule/Page/PriceRuleNewPage.xml | 11 + .../Section/PriceRuleConditionsSection.xml | 20 ++ .../Test/PriceRuleCategoryNestingTest.xml | 63 ++++ 9 files changed, 432 insertions(+), 183 deletions(-) create mode 100644 app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js create mode 100644 app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php create mode 100644 dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Page/PriceRuleNewPage.xml create mode 100644 dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml create mode 100644 dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml diff --git a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js index 5ffc587f65bec..71da78e3c37e2 100644 --- a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js @@ -6,12 +6,13 @@ var config = { map: { '*': { - categoryForm: 'Magento_Catalog/catalog/category/form', - newCategoryDialog: 'Magento_Catalog/js/new-category-dialog', - categoryTree: 'Magento_Catalog/js/category-tree', - productGallery: 'Magento_Catalog/js/product-gallery', - baseImage: 'Magento_Catalog/catalog/base-image-uploader', - productAttributes: 'Magento_Catalog/catalog/product-attributes' + categoryForm: 'Magento_Catalog/catalog/category/form', + newCategoryDialog: 'Magento_Catalog/js/new-category-dialog', + categoryTree: 'Magento_Catalog/js/category-tree', + productGallery: 'Magento_Catalog/js/product-gallery', + baseImage: 'Magento_Catalog/catalog/base-image-uploader', + productAttributes: 'Magento_Catalog/catalog/product-attributes', + categoryCheckboxTree:'Magento_Catalog/js/category-checkbox-tree' } }, deps: [ diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml index 740d389735974..00a1580923a7b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml @@ -5,187 +5,33 @@ */ // @codingStandardsIgnoreFile - -?> - - -
- - - categoryLoader.buildHash = function(node) + diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js new file mode 100644 index 0000000000000..215278ebae575 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js @@ -0,0 +1,277 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'prototype', + 'extjs/ext-tree-checkbox', + 'mage/adminhtml/form' +], function (jQuery) { + 'use strict'; + + return function (config) { + var tree, + options = { + dataUrl: config.dataUrl, + divId: config.divId, + rootVisible: config.rootVisible, + useAjax: config.useAjax, + currentNodeId: config.currentNodeId, + jsFormObject: window[config.jsFormObject], + name: config.name, + checked: config.checked, + allowDrop: config.allowDrop, + rootId: config.rootId, + expanded: config.expanded, + categoryId: config.categoryId, + treeJson: config.treeJson + }, + data = {}, + parameters = {}, + root = {}, + len = 0, + key = '', + i = 0; + + /* eslint-disable */ + /** + * Fix ext compatibility with prototype 1.6 + */ + Ext.lib.Event.getTarget = function (e) {// eslint-disable-line no-undef + var ee = e.browserEvent || e; + + return ee.target ? Event.element(ee) : null; + }; + + /** + * @param {Object} el + * @param {Object} config + */ + Ext.tree.TreePanel.Enhanced = function (el, config) {// eslint-disable-line no-undef + Ext.tree.TreePanel.Enhanced.superclass.constructor.call(this, el, config);// eslint-disable-line no-undef + }; + + Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, {// eslint-disable-line no-undef + /* eslint-enable */ + /** + * @param {Object} config + * @param {Boolean} firstLoad + */ + loadTree: function (config, firstLoad) {// eslint-disable-line no-shadow + parameters = config.parameters, + data = config.data, + root = new Ext.tree.TreeNode(parameters);// eslint-disable-line no-undef + + if (typeof parameters.rootVisible != 'undefined') { + this.rootVisible = parameters.rootVisible * 1; + } + + this.nodeHash = {}; + this.setRootNode(root); + + if (firstLoad) { + this.addListener('click', this.categoryClick.createDelegate(this)); + } + + this.loader.buildCategoryTree(root, data); + this.el.dom.innerHTML = ''; + // render the tree + this.render(); + }, + + /** + * @param {Object} node + */ + categoryClick: function (node) { + node.getUI().check(!node.getUI().checked()); + } + }); + + jQuery(function () { + var categoryLoader = new Ext.tree.TreeLoader({// eslint-disable-line no-undef + dataUrl: config.dataUrl + }); + + /** + * @param {Object} response + * @param {Object} parent + * @param {Function} callback + */ + categoryLoader.processResponse = function (response, parent, callback) { + config = JSON.parse(response.responseText); + + this.buildCategoryTree(parent, config); + + if (typeof callback === 'function') { + callback(this, parent); + } + }; + + /** + * @param {Object} config + * @returns {Object} + */ + categoryLoader.createNode = function (config) {// eslint-disable-line no-shadow + var node; + + config.uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef + + if (config.children && !config.children.length) { + delete config.children; + node = new Ext.tree.AsyncTreeNode(config);// eslint-disable-line no-undef + } else { + node = new Ext.tree.TreeNode(config);// eslint-disable-line no-undef + } + + return node; + }; + + /** + * @param {Object} parent + * @param {Object} config + * @param {Integer} i + */ + categoryLoader.processCategoryTree = function (parent, config, i) {// eslint-disable-line no-shadow + var node, + _node = {}; + + config[i].uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef + + _node = Object.clone(config[i]); + + if (_node.children && !_node.children.length) { + delete _node.children; + node = new Ext.tree.AsyncTreeNode(_node);// eslint-disable-line no-undef + } else { + node = new Ext.tree.TreeNode(config[i]);// eslint-disable-line no-undef + } + parent.appendChild(node); + node.loader = node.getOwnerTree().loader; + + if (_node.children) { + categoryLoader.buildCategoryTree(node, _node.children); + } + }; + + /** + * @param {Object} parent + * @param {Object} config + * @returns {void} + */ + categoryLoader.buildCategoryTree = function (parent, config) {// eslint-disable-line no-shadow + var i = 0; + if (!config) { + return null; + } + + if (parent && config && config.length) { + for (i = 0; i < config.length; i++) { + categoryLoader.processCategoryTree(parent, config, i); + } + } + }; + + /** + * + * @param {Object} hash + * @param {Object} node + * @returns {Object} + */ + categoryLoader.buildHashChildren = function (hash, node) {// eslint-disable-line no-shadow + var i = 0; + // eslint-disable-next-line no-extra-parens + if ((node.childNodes.length > 0) || (node.loaded === false && node.loading === false)) { + hash.children = []; + + for (i = 0, len = node.childNodes.length; i < len; i++) { + /* eslint-disable */ + if (!hash.children) { + hash.children = []; + } + /* eslint-enable */ + hash.children.push(this.buildHash(node.childNodes[i])); + } + } + + return hash; + }; + + /** + * @param {Object} node + * @returns {Object} + */ + categoryLoader.buildHash = function (node) { + var hash = {}; + + hash = this.toArray(node.attributes); + + return categoryLoader.buildHashChildren(hash, node); + }; + + /** + * @param {Object} attributes + * @returns {Object} + */ + categoryLoader.toArray = function (attributes) { + data = {}; + + for (key in attributes) { + + if (attributes[key]) { + data[key] = attributes[key]; + } + } + + return data; + }; + + categoryLoader.on('beforeload', function (treeLoader, node) { + treeLoader.baseParams.id = node.attributes.id; + }); + + /* eslint-disable */ + categoryLoader.on('load', function () { + varienWindowOnload(); + }); + + tree = new Ext.tree.TreePanel.Enhanced(options.divId, { + animate: false, + loader: categoryLoader, + enableDD: false, + containerScroll: true, + selModel: new Ext.tree.CheckNodeMultiSelectionModel(), + rootVisible: options.rootVisible, + useAjax: options.useAjax, + currentNodeId: options.currentNodeId, + addNodeTo: false, + rootUIProvider: Ext.tree.CheckboxNodeUI + }); + + tree.on('check', function (node) { + options.jsFormObject.updateElement.value = this.getChecked().join(', '); + varienElementMethods.setHasChanges(node.getUI().checkbox); + }, tree); + + // set the root node + //jscs:disable requireCamelCaseOrUpperCaseIdentifiers + parameters = { + text: options.name, + draggable: false, + checked: options.checked, + uiProvider: Ext.tree.CheckboxNodeUI, + allowDrop: options.allowDrop, + id: options.rootId, + expanded: options.expanded, + category_id: options.categoryId + }; + //jscs:enable requireCamelCaseOrUpperCaseIdentifiers + + tree.loadTree({ + parameters: parameters, data: options.treeJson + }, true); + /* eslint-enable */ + }); + }; +}); diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js index 5c4be367b9cb3..b094b9818364a 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/rules.js +++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js @@ -137,7 +137,7 @@ define([ }, onSuccess: function (transport) { if (this._processSuccess(transport)) { - $(chooser).update(transport.responseText); + jQuery(chooser).html(transport.responseText); this.showChooserLoaded(chooser, transport); jQuery(chooser).trigger('contentUpdated'); } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php new file mode 100644 index 0000000000000..f413a7d047d62 --- /dev/null +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php @@ -0,0 +1,26 @@ + + + +
+ + diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml new file mode 100644 index 0000000000000..39c6dd6b31968 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Section/PriceRuleConditionsSection.xml @@ -0,0 +1,20 @@ + + +
+ + + + + + + + + + +
+
diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml new file mode 100644 index 0000000000000..9d47a5e823e83 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SalesRule/Test/PriceRuleCategoryNestingTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4ee218691e762465c9a8abc27193731a357c4b01 Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Fri, 4 May 2018 22:07:18 +0300 Subject: [PATCH 173/236] small optimisation in if-condition --- .../Adminhtml/Product/Initialization/Helper/AttributeFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 237168282afae..4641e4fdd7f95 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -31,7 +31,7 @@ public function prepareProductAttributes(Product $product, array $productData, a $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; if ($value === '' && $considerUseDefaultsAttribute) { /** @var $product Product */ - if ((bool)$product->getData($attribute) === (bool)$value) { + if ((bool)$product->getData($attribute) === false) { unset($productData[$attribute]); } } From 1fce0a5eacc884adc53a7791fa0f63af9d7d7b2b Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Sun, 6 May 2018 00:10:09 +0300 Subject: [PATCH 174/236] optimize 2 nested if to one --- .../Product/Initialization/Helper/AttributeFilter.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 4641e4fdd7f95..0c13dd486e325 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -29,11 +29,8 @@ public function prepareProductAttributes(Product $product, array $productData, a { foreach ($productData as $attribute => $value) { $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; - if ($value === '' && $considerUseDefaultsAttribute) { - /** @var $product Product */ - if ((bool)$product->getData($attribute) === false) { - unset($productData[$attribute]); - } + if ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)) { + unset($productData[$attribute]); } } return $productData; From b77ff138442d029e53f15c87d3a91a2676e1e4d2 Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Sun, 6 May 2018 00:24:21 +0300 Subject: [PATCH 175/236] change attributeShouldNotBeUpdated() visibility to protected --- .../Initialization/Helper/AttributeFilter.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 0c13dd486e325..4b503721f9400 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -28,11 +28,24 @@ class AttributeFilter public function prepareProductAttributes(Product $product, array $productData, array $useDefaults) { foreach ($productData as $attribute => $value) { - $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; - if ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)) { + if ($this->attributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) { unset($productData[$attribute]); } } return $productData; } + + /** + * @param $product + * @param $useDefaults + * @param $attribute + * @param $value + * @return bool + */ + protected function attributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) + { + $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; + + return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)); + } } From 163c9da1ea464865a66be38fe82522c4337b1243 Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Sun, 6 May 2018 00:28:25 +0300 Subject: [PATCH 176/236] replace tabs by spaces --- .../Initialization/Helper/AttributeFilter.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 4b503721f9400..52752a53e9646 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -35,17 +35,17 @@ public function prepareProductAttributes(Product $product, array $productData, a return $productData; } - /** - * @param $product - * @param $useDefaults - * @param $attribute - * @param $value - * @return bool - */ + /** + * @param $product + * @param $useDefaults + * @param $attribute + * @param $value + * @return bool + */ protected function attributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) { - $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; + $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; - return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)); + return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)); } } From 75deab25fb1f95608e516f2c28e06f0976a632cc Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Sun, 6 May 2018 00:30:34 +0300 Subject: [PATCH 177/236] add blank row before return statement --- .../Adminhtml/Product/Initialization/Helper/AttributeFilter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 52752a53e9646..f084a6dfad68a 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -32,6 +32,7 @@ public function prepareProductAttributes(Product $product, array $productData, a unset($productData[$attribute]); } } + return $productData; } From b4ec36dd05501f70ca698d52cd320c26412489d3 Mon Sep 17 00:00:00 2001 From: Valerij Ivashchenko Date: Sun, 6 May 2018 17:18:36 +0300 Subject: [PATCH 178/236] rename attributeShouldNotBeUpdated() to isAttributeShouldNotBeUpdated() --- .../Product/Initialization/Helper/AttributeFilter.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index f084a6dfad68a..1ab952a460cd3 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -28,22 +28,22 @@ class AttributeFilter public function prepareProductAttributes(Product $product, array $productData, array $useDefaults) { foreach ($productData as $attribute => $value) { - if ($this->attributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) { + if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) { unset($productData[$attribute]); } } - + return $productData; } /** - * @param $product + * @param Product $product * @param $useDefaults * @param $attribute * @param $value * @return bool */ - protected function attributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) + private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) { $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; From 435d241cbf5552cbae2dc6029e9442cc4e1d5f9f Mon Sep 17 00:00:00 2001 From: Daniel Ruf Date: Sat, 5 May 2018 22:11:25 +0200 Subject: [PATCH 179/236] chore: use random_int() in some places --- .../Backend/Block/Widget/Grid/Column/Renderer/Massaction.php | 2 +- .../Catalog/Model/Indexer/Category/Product/AbstractAction.php | 2 +- app/code/Magento/SalesRule/Model/Coupon/Codegenerator.php | 4 ++-- app/code/Magento/SalesRule/Model/Rule.php | 2 +- lib/internal/Magento/Framework/Encryption/Crypt.php | 2 +- .../Magento/Framework/Setup/BackendFrontnameGenerator.php | 2 +- lib/internal/Magento/Framework/Webapi/ErrorProcessor.php | 2 +- pub/errors/processor.php | 2 +- .../src/Magento/Setup/Model/Address/AddressDataGenerator.php | 2 +- setup/src/Magento/Setup/Model/DataGenerator.php | 4 ++-- .../Magento/Setup/Model/Description/DescriptionGenerator.php | 2 +- .../Setup/Model/Description/DescriptionParagraphGenerator.php | 2 +- .../Setup/Model/Description/DescriptionSentenceGenerator.php | 2 +- setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php | 2 +- .../Model/Description/Mixin/Helper/RandomWordSelector.php | 2 +- .../src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php | 2 +- setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php | 2 +- setup/src/Magento/Setup/Model/Dictionary.php | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php index 320713f8b57c4..a611e91f32f00 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php @@ -65,7 +65,7 @@ public function render(\Magento\Framework\DataObject $row) */ protected function _getCheckboxHtml($value, $checked) { - $id = 'id_' . rand(0, 999); + $id = 'id_' . random_int(0, 999); $html = '
@gutter-width0'' | false | valueDistanse between columnsDistance between columns
Variables for layout columns