From ca9e58264092cc56b993f65789e162612a7d5c9e Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 20 Apr 2017 23:16:22 +0200 Subject: [PATCH 001/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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 d2d397a7acfe29db9bc2d55213eea2f01f393567 Mon Sep 17 00:00:00 2001 From: Dusan Lukic Date: Thu, 16 Nov 2017 14:18:55 +0100 Subject: [PATCH 018/181] 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 019/181] 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 020/181] 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 021/181] 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 022/181] 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 023/181] 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 024/181] 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 025/181] 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 9fcc4dacac4c42090a10fe15218a3140f0863fd2 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Thu, 29 Mar 2018 12:15:25 +0300 Subject: [PATCH 026/181] 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 027/181] 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 475ae2315638ec11e3a2d6754f080ce0ce3148b5 Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Mon, 9 Apr 2018 15:30:35 -0500 Subject: [PATCH 028/181] 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 c2641b576e01a1bcd3618cc222ae823cecd6a54e Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Wed, 11 Apr 2018 17:00:53 +0300 Subject: [PATCH 029/181] 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 feab5c0508e3fb01c2be316164e7da4bdd2687ee Mon Sep 17 00:00:00 2001 From: Roman Ganin Date: Fri, 13 Apr 2018 16:16:30 +0300 Subject: [PATCH 030/181] 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 031/181] 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 032/181] 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 033/181] 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 9a85333f21d09eff1f2e150208e46f06a4058317 Mon Sep 17 00:00:00 2001 From: Eric Bohanon Date: Wed, 25 Apr 2018 13:44:36 -0500 Subject: [PATCH 034/181] 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 035/181] 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 193d007f087c8048303e6aa8680f4af5fd1aa272 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 27 Apr 2018 14:36:53 -0500 Subject: [PATCH 036/181] 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 037/181] 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 7ea06115762dcc4efe9431a67f672692921dc23b Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Mon, 30 Apr 2018 12:11:11 +0300 Subject: [PATCH 038/181] 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 039/181] 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 ca8256e48818a2cd27967e2985a9e7ff3d74d849 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Mon, 30 Apr 2018 13:46:14 +0000 Subject: [PATCH 040/181] 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 041/181] 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 042/181] 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 74778c664f2dad9db52bf64af95f4675708117de Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Tue, 1 May 2018 15:27:08 -0500 Subject: [PATCH 043/181] 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 32a9110fde968459a27622d87f0db65001f7438d Mon Sep 17 00:00:00 2001 From: Stas Kozar Date: Wed, 2 May 2018 12:43:17 +0300 Subject: [PATCH 044/181] 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 e429f0ec77b9330bcda81a41a79a4d4e90921a8a Mon Sep 17 00:00:00 2001 From: Dan Mooney Date: Wed, 2 May 2018 13:24:04 -0500 Subject: [PATCH 045/181] 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 046/181] 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 047/181] 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 048/181] 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 047668991ad2376b7fe15e64dea74031dabb0cf1 Mon Sep 17 00:00:00 2001 From: Ievgen Shakhsuvarov Date: Thu, 3 May 2018 12:51:23 +0300 Subject: [PATCH 049/181] 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 050/181] 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 051/181] 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 052/181] 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 eac772a584bc384dc24ad217d7964a33e0203129 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Thu, 3 May 2018 10:18:58 -0500 Subject: [PATCH 053/181] 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 8a038ca43f0ab2d92831981e435f8e457a7bca40 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Fri, 4 May 2018 09:33:53 +0300 Subject: [PATCH 054/181] 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 055/181] 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 056/181] 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 057/181] 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 058/181] 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 059/181] 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 060/181] 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 061/181] 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 062/181] 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 063/181] 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 064/181] 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 065/181] 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 066/181] 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 067/181] [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 068/181] [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 4113f26f34e1c2e7035b0f294999aa8add6e6c52 Mon Sep 17 00:00:00 2001 From: gwharton Date: Sun, 6 May 2018 11:00:08 +0100 Subject: [PATCH 069/181] 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 070/181] 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 071/181] 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 072/181] 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 073/181] 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 074/181] 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 @@