diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index 1bb91fd2455e0c679c70df0321b0a579bc34e910..74e1566a8dc5ba6f4691574491bbb6b7b65dcc43 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -63,6 +63,7 @@ define([ } if (res.minicart) { $(self.options.minicartSelector).replaceWith(res.minicart); + $(self.options.minicartSelector).trigger('contentUpdated'); } if (res.product && res.product.statusText) { $(self.options.productStatusSelector) diff --git a/app/code/Magento/GiftMessage/Model/GiftMessageManager.php b/app/code/Magento/GiftMessage/Model/GiftMessageManager.php index 7fc655d323ab3f2816c55ff433071d19d05ae3f9..55c2b0020c15cea8c26846897e7357693f03e515 100644 --- a/app/code/Magento/GiftMessage/Model/GiftMessageManager.php +++ b/app/code/Magento/GiftMessage/Model/GiftMessageManager.php @@ -79,6 +79,8 @@ class GiftMessageManager $message['to'] )->setMessage( $message['message'] + )->setCustomerId( + $quote->getCustomerId() )->save(); $entity->setGiftMessageId($giftMessage->getId())->save(); diff --git a/app/code/Magento/GiftMessage/Model/Observer.php b/app/code/Magento/GiftMessage/Model/Observer.php index 4c3f7b8bd57c20ba55c88953c65e16aafd8b3d3b..696b4f38f5f6ae96d869838106d96d524bafeb81 100644 --- a/app/code/Magento/GiftMessage/Model/Observer.php +++ b/app/code/Magento/GiftMessage/Model/Observer.php @@ -39,15 +39,27 @@ class Observer extends \Magento\Framework\Object /** * Set gift messages to order from quote address * - * @param \Magento\Framework\Object $observer + * @param \Magento\Framework\Event\Observer $observer * @return $this */ - public function salesEventConvertQuoteToOrder($observer) + public function salesEventQuoteSubmitBefore($observer) { $observer->getEvent()->getOrder()->setGiftMessageId($observer->getEvent()->getQuote()->getGiftMessageId()); return $this; } + /** + * Set gift message to order from address in multiple addresses checkout. + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function multishippingEventCreateOrders($observer) + { + $observer->getEvent()->getOrder()->setGiftMessageId($observer->getEvent()->getAddress()->getGiftMessageId()); + return $this; + } + /** * Duplicates giftmessage from order to quote on import or reorder * diff --git a/app/code/Magento/GiftMessage/Test/Unit/Model/GiftMessageManagerTest.php b/app/code/Magento/GiftMessage/Test/Unit/Model/GiftMessageManagerTest.php index 03139be4a20fb8ada5792188b6fd09b486b1a131..e44dca6dec3c1d412b24f7110c0a695893267aa6 100644 --- a/app/code/Magento/GiftMessage/Test/Unit/Model/GiftMessageManagerTest.php +++ b/app/code/Magento/GiftMessage/Test/Unit/Model/GiftMessageManagerTest.php @@ -72,7 +72,9 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase 'getAddressById', 'getBillingAddress', 'getShippingAddress', - '__wakeup'], + '__wakeup', + 'getCustomerId' + ], [], '', false); @@ -116,6 +118,7 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase 'setSender', 'setRecipient', 'setMessage', + 'setCustomerId', 'getSender', 'getRecipient', 'getMessage', @@ -184,6 +187,7 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ], ], ]; + $customerId = 42; $this->messageFactoryMock->expects($this->once()) ->method('create') @@ -201,6 +205,11 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ->method('setRecipient') ->with('recipient') ->will($this->returnValue($this->giftMessageMock)); + $this->quoteMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->giftMessageMock->expects($this->once()) + ->method('setCustomerId') + ->with($customerId) + ->will($this->returnValue($this->giftMessageMock)); $this->giftMessageMock->expects($this->once()) ->method('setMessage') ->with('message') @@ -229,6 +238,7 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ], ], ]; + $customerId = 42; $this->messageFactoryMock->expects($this->once()) ->method('create') @@ -250,6 +260,11 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ->method('setMessage') ->with('message') ->will($this->returnValue($this->giftMessageMock)); + $this->quoteMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->giftMessageMock->expects($this->once()) + ->method('setCustomerId') + ->with($customerId) + ->will($this->returnValue($this->giftMessageMock)); $this->giftMessageMock->expects($this->once())->method('save'); $this->giftMessageMock->expects($this->once())->method('getId')->will($this->returnValue(33)); $this->quoteAddressMock->expects($this->once()) @@ -273,6 +288,7 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ], ], ]; + $customerId = 42; $this->messageFactoryMock->expects($this->once()) ->method('create') @@ -298,6 +314,11 @@ class GiftMessageManagerTest extends \PHPUnit_Framework_TestCase ->method('setMessage') ->with('message') ->will($this->returnValue($this->giftMessageMock)); + $this->quoteMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->giftMessageMock->expects($this->once()) + ->method('setCustomerId') + ->with($customerId) + ->will($this->returnValue($this->giftMessageMock)); $this->giftMessageMock->expects($this->once())->method('save'); $this->giftMessageMock->expects($this->once())->method('getId')->will($this->returnValue(33)); $this->quoteAddressItemMock->expects($this->once()) diff --git a/app/code/Magento/GiftMessage/Test/Unit/Model/ObserverTest.php b/app/code/Magento/GiftMessage/Test/Unit/Model/ObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cf2fb943d417fb05c4a630e282ab7e716ad51279 --- /dev/null +++ b/app/code/Magento/GiftMessage/Test/Unit/Model/ObserverTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\GiftMessage\Test\Unit\Model; + +class ObserverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\GiftMessage\Model\Observer + */ + protected $model; + + protected function setUp() + { + $giftMessageFactoryMock = $this->getMock('\Magento\GiftMessage\Model\MessageFactory', [], [], '', false); + $giftMessageMock = $this->getMock('\Magento\GiftMessage\Helper\Message', [], [], '', false); + $this->model = new \Magento\GiftMessage\Model\Observer($giftMessageFactoryMock, $giftMessageMock); + } + + public function testMultishippingEventCreateOrders() + { + $giftMessageId = 42; + $observerMock = $this->getMock('\Magento\Framework\Event\Observer'); + $eventMock = $this->getMock('\Magento\Framework\Event', ['getOrder', 'getAddress']); + $addressMock = $this->getMock('\Magento\Quote\Model\Quote\Address', ['getGiftMessageId'], [], '', false); + $orderMock = $this->getMock('\Magento\Sales\Model\Order', ['setGiftMessageId'], [], '', false); + $observerMock->expects($this->exactly(2))->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getAddress')->willReturn($addressMock); + $addressMock->expects($this->once())->method('getGiftMessageId')->willReturn($giftMessageId); + $eventMock->expects($this->once())->method('getOrder')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setGiftMessageId')->with($giftMessageId); + $this->assertEquals($this->model, $this->model->multishippingEventCreateOrders($observerMock)); + } + + public function testSalesEventQuoteSubmitBefore() + { + $giftMessageId = 42; + $observerMock = $this->getMock('\Magento\Framework\Event\Observer'); + $eventMock = $this->getMock('\Magento\Framework\Event', ['getOrder', 'getQuote']); + $quoteMock = $this->getMock('\Magento\Quote\Model\Quote', ['getGiftMessageId'], [], '', false); + $orderMock = $this->getMock('\Magento\Sales\Model\Order', ['setGiftMessageId'], [], '', false); + $observerMock->expects($this->exactly(2))->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getQuote')->willReturn($quoteMock); + $quoteMock->expects($this->once())->method('getGiftMessageId')->willReturn($giftMessageId); + $eventMock->expects($this->once())->method('getOrder')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setGiftMessageId')->with($giftMessageId); + $this->assertEquals($this->model, $this->model->salesEventQuoteSubmitBefore($observerMock)); + } +} diff --git a/app/code/Magento/GiftMessage/etc/adminhtml/events.xml b/app/code/Magento/GiftMessage/etc/adminhtml/events.xml index 68ce9ea43da8b8b82ce7a8ed2fdca3689b40f32e..0332fc0e59044672cbf075d62dc922b74bfe745a 100644 --- a/app/code/Magento/GiftMessage/etc/adminhtml/events.xml +++ b/app/code/Magento/GiftMessage/etc/adminhtml/events.xml @@ -6,8 +6,8 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd"> - <event name="sales_convert_quote_to_order"> - <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="salesEventConvertQuoteToOrder" shared="false" /> + <event name="sales_model_service_quote_submit_before"> + <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="salesEventQuoteSubmitBefore" shared="false" /> </event> <event name="adminhtml_sales_order_create_create_order"> <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="checkoutEventCreateOrder" shared="false" /> diff --git a/app/code/Magento/GiftMessage/etc/frontend/events.xml b/app/code/Magento/GiftMessage/etc/frontend/events.xml index bceffb520f1a58ac23051b986126e1e3aa6fe9f6..d439bfcbcd29eef354cf45eee1f6de35750a30b6 100644 --- a/app/code/Magento/GiftMessage/etc/frontend/events.xml +++ b/app/code/Magento/GiftMessage/etc/frontend/events.xml @@ -6,10 +6,13 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd"> - <event name="sales_convert_quote_to_order"> - <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="salesEventConvertQuoteToOrder" shared="false" /> + <event name="sales_model_service_quote_submit_before"> + <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="salesEventQuoteSubmitBefore" shared="false" /> </event> <event name="sales_convert_order_to_quote"> <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="salesEventOrderToQuote" shared="false" /> </event> + <event name="checkout_type_multishipping_create_orders_single"> + <observer name="giftmessage" instance="Magento\GiftMessage\Model\Observer" method="multishippingEventCreateOrders" shared="false" /> + </event> </config> diff --git a/app/code/Magento/Quote/Model/QueryResolver.php b/app/code/Magento/Quote/Model/QueryResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..e1c14fe743f4fd3c2e688cdc18d9d68cedd1875e --- /dev/null +++ b/app/code/Magento/Quote/Model/QueryResolver.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Quote\Model; + +use Magento\Framework\Config\CacheInterface; +use Magento\Framework\App\Resource\ConfigInterface; + +class QueryResolver +{ + /** + * @var array + */ + private $data = []; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var CacheInterface + */ + private $cache; + + /** + * @var string + */ + private $cacheId; + + /** + * Cache tags + * + * @var array + */ + private $cacheTags = []; + + /** + * @param ConfigInterface $config + * @param CacheInterface $cache + * @param string $cacheId + */ + public function __construct( + ConfigInterface $config, + CacheInterface $cache, + $cacheId = 'connection_config_cache' + ) { + $this->config = $config; + $this->cache = $cache; + $this->cacheId = $cacheId; + } + + /** + * Get flag value + * + * @return bool + */ + public function isSingleQuery() + { + if (!isset($this->data['checkout'])) { + $this->initData(); + } + return $this->data['checkout']; + } + + /** + * Initialise data for configuration + * @return void + */ + protected function initData() + { + $data = $this->cache->load($this->cacheId); + if (false === $data) { + $singleQuery = $this->config->getConnectionName('checkout_setup') == 'default' ? true : false; + $data['checkout'] = $singleQuery; + $this->cache->save(serialize($data), $this->cacheId, $this->cacheTags); + } else { + $data = unserialize($data); + } + $this->merge($data); + } + + /** + * Merge config data to the object + * + * @param array $config + * @return void + */ + public function merge(array $config) + { + $this->data = array_replace_recursive($this->data, $config); + } +} diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 337710338c960011dd1bee5d2c3587e01adb69cf..0f53de853e9c123685bab4ffaac707a6d55ce94d 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -236,8 +236,15 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface public function placeOrder($cartId) { $quote = $this->quoteRepository->getActive($cartId); - $order = $this->submit($quote); - return $order->getId(); + + if ($quote->getCheckoutMethod() === 'guest') { + $quote->setCustomerId(null); + $quote->setCustomerEmail($quote->getBillingAddress()->getEmail()); + $quote->setCustomerIsGuest(true); + $quote->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID); + } + + return $this->submit($quote)->getId(); } /** diff --git a/app/code/Magento/Quote/Test/Unit/Model/QueryResolverTest.php b/app/code/Magento/Quote/Test/Unit/Model/QueryResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8aaaf69b1b5422b54f6964dd20cd21658fc32bd3 --- /dev/null +++ b/app/code/Magento/Quote/Test/Unit/Model/QueryResolverTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Quote\Test\Unit\Model; + +class QueryResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Quote\Model\QueryResolver + */ + protected $quoteResolver; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $configMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheMock; + + protected function setUp() + { + $this->configMock = $this->getMock('Magento\Framework\App\Resource\ConfigInterface'); + $this->cacheMock = $this->getMock('Magento\Framework\Config\CacheInterface'); + $this->quoteResolver = new \Magento\Quote\Model\QueryResolver( + $this->configMock, + $this->cacheMock, + 'connection_config_cache' + ); + + } + + public function testIsSingleQueryWhenDataWereCached() + { + $queryData['checkout'] = true; + $this->cacheMock + ->expects($this->once()) + ->method('load') + ->with('connection_config_cache') + ->willReturn(serialize($queryData)); + $this->assertTrue($this->quoteResolver->isSingleQuery()); + } + + public function testIsSingleQueryWhenDataNotCached() + { + $queryData['checkout'] = true; + $this->cacheMock + ->expects($this->once()) + ->method('load') + ->with('connection_config_cache') + ->willReturn(false); + $this->configMock + ->expects($this->once()) + ->method('getConnectionName') + ->with('checkout_setup') + ->willReturn('default'); + $this->cacheMock + ->expects($this->once()) + ->method('save') + ->with(serialize($queryData), 'connection_config_cache', []); + $this->assertTrue($this->quoteResolver->isSingleQuery()); + } + + public function testIsSingleQueryWhenSeveralConnectionsExist() + { + $queryData['checkout'] = false; + $this->cacheMock + ->expects($this->once()) + ->method('load') + ->with('connection_config_cache') + ->willReturn(false); + $this->configMock + ->expects($this->once()) + ->method('getConnectionName') + ->with('checkout_setup') + ->willReturn('checkout'); + $this->cacheMock + ->expects($this->once()) + ->method('save') + ->with(serialize($queryData), 'connection_config_cache', []); + $this->assertFalse($this->quoteResolver->isSingleQuery()); + } +} diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php index 91acfc6402af36e51806cfeba16b92329cfc2a38..522734a324ad98bc080654f4ceaee3e3a6da295e 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php @@ -579,6 +579,62 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $this->assertEquals($order, $this->model->submit($quote, $orderData)); } + /** + * //Last method throws exception because class method 'submit()' already covered. + * + * @expectedException \Exception + * @expectedExceptionMessage Quote prepared for guest customer. + */ + public function testPlaceOrderIfCustomerIsQuest() + { + $cartId = 100; + $email = 'email@mail.com'; + $quoteMock = $this->getMock( + 'Magento\Quote\Model\Quote', + [ + 'getCheckoutMethod', + 'setCustomerId', + 'setCustomerEmail', + 'getBillingAddress', + 'setCustomerIsGuest', + 'setCustomerGroupId' + ], + [], + '', + false + ); + $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with($cartId)->willReturn($quoteMock); + + $quoteMock->expects($this->once()) + ->method('getCheckoutMethod') + ->willReturn(\Magento\Checkout\Model\Type\Onepage::METHOD_GUEST); + $quoteMock->expects($this->once())->method('setCustomerId')->with(null)->willReturnSelf(); + $quoteMock->expects($this->once())->method('setCustomerEmail')->with($email)->willReturnSelf(); + + $addressMock = $this->getMock('\Magento\Quote\Model\Quote\Address', ['getEmail'], [], '', false); + $addressMock->expects($this->once())->method('getEmail')->willReturn($email); + $quoteMock->expects($this->once())->method('getBillingAddress')->with()->willReturn($addressMock); + + $quoteMock->expects($this->once())->method('setCustomerIsGuest')->with(true)->willReturnSelf(); + $quoteMock->expects($this->once()) + ->method('setCustomerGroupId') + ->with(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID) + ->willThrowException(new \Exception('Quote prepared for guest customer.')); + + $this->model->placeOrder($cartId); + } + + /** + * @param $isGuest + * @param $isVirtual + * @param Quote\Address $billingAddress + * @param Quote\Payment $payment + * @param $customerId + * @param $id + * @param array $quoteItems + * @param Quote\Address $shippingAddress + * @return \PHPUnit_Framework_MockObject_MockObject + */ protected function getQuote( $isGuest, $isVirtual, @@ -652,6 +708,16 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase return $quote; } + /** + * @param \Magento\Sales\Api\Data\OrderInterface $baseOrder + * @param \Magento\Sales\Api\Data\OrderAddressInterface $billingAddress + * @param array $addresses + * @param array $payments + * @param array $items + * @param $quoteId + * @param \Magento\Sales\Api\Data\OrderAddressInterface $shippingAddress + * @return \PHPUnit_Framework_MockObject_MockObject + */ protected function prepareOrderFactory( \Magento\Sales\Api\Data\OrderInterface $baseOrder, \Magento\Sales\Api\Data\OrderAddressInterface $billingAddress, @@ -659,7 +725,8 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase array $payments, array $items, $quoteId, - \Magento\Sales\Api\Data\OrderAddressInterface $shippingAddress = null + \Magento\Sales\Api\Data\OrderAddressInterface $shippingAddress = null, + $customerId = null ) { $order = $this->getMock( 'Magento\Sales\Model\Order', @@ -680,6 +747,11 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase if ($shippingAddress) { $order->expects($this->once())->method('setShippingAddress')->with($shippingAddress); } + if ($customerId) { + $this->orderFactory->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + } $order->expects($this->any())->method('getAddressesCollection'); $order->expects($this->any())->method('getAddresses'); $order->expects($this->any())->method('getBillingAddress')->willReturn(false); diff --git a/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Abandoned/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Abandoned/Grid.php index 4ef5903836cc0b82e097b768f1f59f7ec672cd6a..d40166dff4bb52049017b1340ce7cfd9c35de069 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Abandoned/Grid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Abandoned/Grid.php @@ -64,7 +64,9 @@ class Grid extends \Magento\Reports\Block\Adminhtml\Grid\Shopcart } $this->setCollection($collection); - return parent::_prepareCollection(); + parent::_prepareCollection(); + $this->getCollection()->resolveCustomerNames(); + return $this; } /** diff --git a/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Product/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Product/Grid.php index a4837b8f21861bff88ee59dd564d207c4eab75a5..6de66a60e04a8c28d281e4f55f1fb0cca6a388dc 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Product/Grid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Shopcart/Product/Grid.php @@ -18,19 +18,27 @@ class Grid extends \Magento\Reports\Block\Adminhtml\Grid\Shopcart */ protected $_quotesFactory; + /** + * @var \Magento\Quote\Model\QueryResolver + */ + protected $queryResolver; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Helper\Data $backendHelper - * @param \Magento\Reports\Model\Resource\Quote\CollectionFactory $quotesFactory + * @param \Magento\Reports\Model\Resource\Quote\CollectionFactoryInterface $quotesFactory + * @param \Magento\Quote\Model\QueryResolver $queryResolver * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Backend\Helper\Data $backendHelper, - \Magento\Reports\Model\Resource\Quote\CollectionFactory $quotesFactory, + \Magento\Reports\Model\Resource\Quote\CollectionFactoryInterface $quotesFactory, + \Magento\Quote\Model\QueryResolver $queryResolver, array $data = [] ) { $this->_quotesFactory = $quotesFactory; + $this->queryResolver = $queryResolver; parent::__construct($context, $backendHelper, $data); } @@ -48,11 +56,15 @@ class Grid extends \Magento\Reports\Block\Adminhtml\Grid\Shopcart */ protected function _prepareCollection() { - /** @var $collection \Magento\Reports\Model\Resource\Quote\Collection */ $collection = $this->_quotesFactory->create(); - $collection->prepareForProductsInCarts()->setSelectCountSqlType( - \Magento\Reports\Model\Resource\Quote\Collection::SELECT_COUNT_SQL_TYPE_CART - ); + if ($this->queryResolver->isSingleQuery()) { + $collection->prepareForProductsInCarts(); + $collection->setSelectCountSqlType( + \Magento\Reports\Model\Resource\Quote\Collection::SELECT_COUNT_SQL_TYPE_CART + ); + } else { + $collection->prepareActiveCartItems(); + } $this->setCollection($collection); return parent::_prepareCollection(); } diff --git a/app/code/Magento/Reports/Model/Resource/Product/Collection.php b/app/code/Magento/Reports/Model/Resource/Product/Collection.php index 0462d4d727f8e127b1b36f2501b6ee9a8b2ead59..3e77d75e16eb6f2f451477f1c89ceb4f907b4e8e 100644 --- a/app/code/Magento/Reports/Model/Resource/Product/Collection.php +++ b/app/code/Magento/Reports/Model/Resource/Product/Collection.php @@ -33,11 +33,11 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection protected $_productEntityTableName; /** - * Product entity type identifier + * Product entity attribute set identifier * * @var int */ - protected $_productEntityTypeId; + protected $_productEntityAttributeSetId; /** * Select count @@ -110,7 +110,7 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection ) { $this->setProductEntityId($product->getEntityIdField()); $this->setProductEntityTableName($product->getEntityTable()); - $this->setProductEntityTypeId($product->getTypeId()); + $this->setProductAttributeSetId($product->getEntityType()->getDefaultAttributeSetId()); parent::__construct( $entityFactory, $logger, @@ -140,6 +140,7 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection /** * Set Type for COUNT SQL Select * + * @codeCoverageIgnoreStart * @param int $type * @return $this */ @@ -194,26 +195,27 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection } /** - * Set product entity type id + * Get product attribute set id * - * @param int $value - * @return $this + * @return int */ - public function setProductEntityTypeId($value) + public function getProductAttributeSetId() { - $this->_productEntityTypeId = $value; - return $this; + return $this->_productEntityAttributeSetId; } /** - * Get product entity type id + * Set product attribute set id * - * @return int + * @param int $value + * @return $this */ - public function getProductEntityTypeId() + public function setProductAttributeSetId($value) { - return $this->_productEntityTypeId; + $this->_productEntityAttributeSetId = $value; + return $this; } + //@codeCoverageIgnoreEnd /** * Join fields @@ -232,7 +234,7 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection /** * Get select count sql * - * @return string + * @return \Zend_Db_Select */ public function getSelectCountSql() { @@ -261,39 +263,6 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection return $countSelect; } - /** - * Add carts count - * - * @return $this - */ - public function addCartsCount() - { - $countSelect = clone $this->getSelect(); - $countSelect->reset(); - - $countSelect->from( - ['quote_items' => $this->getTable('quote_item')], - 'COUNT(*)' - )->join( - ['quotes' => $this->getTable('quote')], - 'quotes.entity_id = quote_items.quote_id AND quotes.is_active = 1', - [] - )->where( - "quote_items.product_id = e.entity_id" - ); - - $this->getSelect()->columns( - ["carts" => "({$countSelect})"] - )->group( - "e.{$this->getProductEntityId()}" - )->having( - 'carts > ?', - 0 - ); - - return $this; - } - /** * Add orders count * @@ -351,7 +320,7 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection $productJoinCondition = [ $adapter->quoteInto('(e.type_id NOT IN (?))', $compositeTypeIds), 'e.entity_id = order_items.product_id', - $adapter->quoteInto('e.entity_type_id = ?', $this->getProductEntityTypeId()), + $adapter->quoteInto('e.attribute_set_id = ?', $this->getProductAttributeSetId()), ]; if ($from != '' && $to != '') { @@ -371,7 +340,6 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection implode(' AND ', $productJoinCondition), [ 'entity_id' => 'order_items.product_id', - 'entity_type_id' => 'e.entity_type_id', 'attribute_set_id' => 'e.attribute_set_id', 'type_id' => 'e.type_id', 'sku' => 'e.sku', @@ -434,7 +402,10 @@ class Collection extends \Magento\Catalog\Model\Resource\Product\Collection ['views' => 'COUNT(report_table_views.event_id)'] )->join( ['e' => $this->getProductEntityTableName()], - 'e.entity_id = report_table_views.object_id' + $this->getConnection()->quoteInto( + 'e.entity_id = report_table_views.object_id AND e.attribute_set_id = ?', + $this->getProductAttributeSetId() + ) )->where( 'report_table_views.event_type_id = ?', $productViewEvent diff --git a/app/code/Magento/Reports/Model/Resource/Quote/Collection.php b/app/code/Magento/Reports/Model/Resource/Quote/Collection.php index a2deabc2d008f4b33a05597ede7ac9421c79c660..38ba00038520a828469d8d5c1cda8976c9a3b0b3 100644 --- a/app/code/Magento/Reports/Model/Resource/Quote/Collection.php +++ b/app/code/Magento/Reports/Model/Resource/Quote/Collection.php @@ -98,14 +98,18 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection )->addFieldToFilter( 'main_table.is_active', '1' + )->addFieldToFilter( + 'main_table.customer_id', + ['neq' => null] )->addSubtotal( $storeIds, $filter - )->addCustomerData( - $filter )->setOrder( 'updated_at' ); + if (isset($filter['email']) || isset($filter['customer_name'])) { + $this->addCustomerData($filter); + } if (is_array($storeIds) && !empty($storeIds)) { $this->addFieldToFilter('store_id', ['in' => $storeIds]); } @@ -127,14 +131,6 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection $productAttrPriceId = (int)$productAttrPrice->getAttributeId(); $productAttrPriceTable = $productAttrPrice->getBackend()->getTable(); - $ordersSubSelect = clone $this->getSelect(); - $ordersSubSelect->reset()->from( - ['oi' => $this->getTable('sales_order_item')], - ['orders' => new \Zend_Db_Expr('COUNT(1)'), 'product_id'] - )->group( - 'oi.product_id' - ); - $this->getSelect()->useStraightJoin( true )->reset( @@ -149,15 +145,16 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection null )->joinInner( ['product_name' => $productAttrNameTable], - "product_name.entity_id = e.entity_id\n AND product_name.attribute_id = {$productAttrNameId}\n AND product_name.store_id = " . - \Magento\Store\Model\Store::DEFAULT_STORE_ID, + 'product_name.entity_id = e.entity_id' + . ' AND product_name.attribute_id = ' . $productAttrNameId + . ' AND product_name.store_id = ' . \Magento\Store\Model\Store::DEFAULT_STORE_ID, ['name' => 'product_name.value'] )->joinInner( ['product_price' => $productAttrPriceTable], "product_price.entity_id = e.entity_id AND product_price.attribute_id = {$productAttrPriceId}", ['price' => new \Zend_Db_Expr('product_price.value * main_table.base_to_global_rate')] )->joinLeft( - ['order_items' => new \Zend_Db_Expr(sprintf('(%s)', $ordersSubSelect))], + ['order_items' => new \Zend_Db_Expr(sprintf('(%s)', $this->getOrdersSubSelect()))], 'order_items.product_id = e.entity_id', [] )->columns( @@ -176,6 +173,24 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection return $this; } + /** + * Orders quantity subselect + * + * @return \Magento\Framework\DB\Select + */ + protected function getOrdersSubSelect() + { + $ordersSubSelect = clone $this->getSelect(); + $ordersSubSelect->reset()->from( + ['oi' => $this->getTable('sales_order_item')], + ['orders' => new \Zend_Db_Expr('COUNT(1)'), 'product_id'] + )->group( + 'oi.product_id' + ); + + return $ordersSubSelect; + } + /** * Add store ids to filter * @@ -191,64 +206,25 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection /** * Add customer data * - * @param unknown_type $filter + * @param array|null $filter * @return $this */ public function addCustomerData($filter = null) { - $attrFirstname = $this->_customerResource->getAttribute('firstname'); - $attrFirstnameId = (int)$attrFirstname->getAttributeId(); - $attrFirstnameTableName = $attrFirstname->getBackend()->getTable(); - - $attrLastname = $this->_customerResource->getAttribute('lastname'); - $attrLastnameId = (int)$attrLastname->getAttributeId(); - $attrLastnameTableName = $attrLastname->getBackend()->getTable(); - - $attrEmail = $this->_customerResource->getAttribute('email'); - $attrEmailTableName = $attrEmail->getBackend()->getTable(); - - $adapter = $this->getSelect()->getAdapter(); - $customerName = $adapter->getConcatSql(['cust_fname.value', 'cust_lname.value'], ' '); - $this->getSelect()->joinInner( - ['cust_email' => $attrEmailTableName], - 'cust_email.entity_id = main_table.customer_id', - ['email' => 'cust_email.email'] - )->joinInner( - ['cust_fname' => $attrFirstnameTableName], - implode( - ' AND ', - [ - 'cust_fname.entity_id = main_table.customer_id', - $adapter->quoteInto('cust_fname.attribute_id = ?', (int)$attrFirstnameId) - ] - ), - ['firstname' => 'cust_fname.value'] - )->joinInner( - ['cust_lname' => $attrLastnameTableName], - implode( - ' AND ', - [ - 'cust_lname.entity_id = main_table.customer_id', - $adapter->quoteInto('cust_lname.attribute_id = ?', (int)$attrLastnameId) - ] - ), - ['lastname' => 'cust_lname.value', 'customer_name' => $customerName] - ); - - $this->_joinedFields['customer_name'] = $customerName; - $this->_joinedFields['email'] = 'cust_email.email'; - - if ($filter) { - if (isset($filter['customer_name'])) { - $likeExpr = '%' . $filter['customer_name'] . '%'; - $this->getSelect()->where($this->_joinedFields['customer_name'] . ' LIKE ?', $likeExpr); - } - if (isset($filter['email'])) { - $likeExpr = '%' . $filter['email'] . '%'; - $this->getSelect()->where($this->_joinedFields['email'] . ' LIKE ?', $likeExpr); - } + $customersSelect = $this->_customerResource->getReadConnection()->select(); + $customersSelect->from(['customer' => 'customer_entity'], 'entity_id'); + if (isset($filter['customer_name'])) { + $customersSelect = $this->getCustomerNames($customersSelect); + $customerName = $customersSelect->getAdapter()->getConcatSql(['cust_fname.value', 'cust_lname.value'], ' '); + $customersSelect->where( + $customerName . ' LIKE ?', '%' . $filter['customer_name'] . '%' + ); } - + if (isset($filter['email'])) { + $customersSelect->where('customer.email LIKE ?', '%' . $filter['email'] . '%'); + } + $filteredCustomers = $this->_customerResource->getReadConnection()->fetchCol($customersSelect); + $this->getSelect()->where('main_table.customer_id IN (?)', $filteredCustomers); return $this; } @@ -314,4 +290,57 @@ class Collection extends \Magento\Quote\Model\Resource\Quote\Collection return $countSelect; } + + /** + * @param \Magento\Framework\DB\Select $select + * @return \Magento\Framework\DB\Select + */ + protected function getCustomerNames($select) + { + $attrFirstname = $this->_customerResource->getAttribute('firstname'); + $attrFirstnameId = (int)$attrFirstname->getAttributeId(); + $attrFirstnameTableName = $attrFirstname->getBackend()->getTable(); + $attrLastname = $this->_customerResource->getAttribute('lastname'); + $attrLastnameId = (int)$attrLastname->getAttributeId(); + $attrLastnameTableName = $attrLastname->getBackend()->getTable(); + $select->joinInner( + ['cust_fname' => $attrFirstnameTableName], + 'customer.entity_id = cust_fname.entity_id', + ['firstname' => 'cust_fname.value'] + )->joinInner( + ['cust_lname' => $attrLastnameTableName], + 'customer.entity_id = cust_lname.entity_id', + ['lastname' => 'cust_lname.value'] + )->where( + 'cust_fname.attribute_id = ?', (int)$attrFirstnameId + )->where( + 'cust_lname.attribute_id = ?', (int)$attrLastnameId + ); + return $select; + } + + /** + * Resolve customers data based on ids quote table. + * + * @return void + */ + public function resolveCustomerNames() + { + $select = $this->_customerResource->getReadConnection()->select(); + $customerName = $select->getAdapter()->getConcatSql(['cust_fname.value', 'cust_lname.value'], ' '); + + $select->from( + ['customer' => 'customer_entity'] + )->columns( + ['customer_name' => $customerName] + )->where( + 'customer.entity_id IN (?)', array_column($this->getData(), 'customer_id') + ); + $customersData = $select->getAdapter()->fetchAll($this->getCustomerNames($select)); + + foreach($this->getItems() as $item) { + $item->setData(array_merge($item->getData(), current($customersData))); + next($customersData); + } + } } diff --git a/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactory.php b/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..c08c6e1dd00759b85bf56409a58890d5a9539626 --- /dev/null +++ b/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactory.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Reports\Model\Resource\Quote; + +/** + * Factory class for @see \Magento\Reports\Model\Resource\Quote\Collection + * + * @codeCoverageIgnore + */ +class CollectionFactory implements \Magento\Reports\Model\Resource\Quote\CollectionFactoryInterface +{ + /** + * Object Manager instance + * + * @var \Magento\Framework\ObjectManagerInterface + */ + protected $_objectManager = null; + + /** + * Instance name to create + * + * @var string + */ + protected $_instanceName = null; + + /** + * Factory constructor + * + * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param string $instanceName + */ + public function __construct( + \Magento\Framework\ObjectManagerInterface $objectManager, + $instanceName = 'Magento\\Reports\\Model\\Resource\\Quote\\Collection' + ) { + $this->_objectManager = $objectManager; + $this->_instanceName = $instanceName; + } + + /** + *{ @inheritdoc) + */ + public function create(array $data = []) + { + return $this->_objectManager->create($this->_instanceName, $data); + } +} diff --git a/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactoryInterface.php b/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0d3a368ebe5f64c54e602c62b5f7aa8ae755a6c9 --- /dev/null +++ b/app/code/Magento/Reports/Model/Resource/Quote/CollectionFactoryInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Reports\Model\Resource\Quote; + +interface CollectionFactoryInterface +{ + /** + * Create class instance with specified parameters + * + * @param array $data + * @return \Magento\Reports\Model\Resource\Quote\Collection + */ + public function create(array $data = []); +} diff --git a/app/code/Magento/Reports/Model/Resource/Shopcart/Product/Collection.php b/app/code/Magento/Reports/Model/Resource/Shopcart/Product/Collection.php deleted file mode 100644 index 8c251de78d6bd554fe73729dcef77b779fcd000d..0000000000000000000000000000000000000000 --- a/app/code/Magento/Reports/Model/Resource/Shopcart/Product/Collection.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Shopingcart Products Report collection - * - * @author Magento Core Team <core@magentocommerce.com> - */ -namespace Magento\Reports\Model\Resource\Shopcart\Product; - -class Collection extends \Magento\Reports\Model\Resource\Product\Collection -{ - /** - * Join fields - * - * @return $this - */ - protected function _joinFields() - { - parent::_joinFields(); - $this->addAttributeToSelect('price')->addCartsCount()->addOrdersCount(); - - return $this; - } - - /** - * Set date range - * - * @param string $from - * @param string $to - * @return $this - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function setDateRange($from, $to) - { - $this->getSelect()->reset(); - return $this; - } -} diff --git a/app/code/Magento/Reports/etc/di.xml b/app/code/Magento/Reports/etc/di.xml index dee2319661d6822fa08865b4afafdd45bd38ce6c..0e7a4e0a11b075e1265c9fd5f503b53105b249c6 100644 --- a/app/code/Magento/Reports/etc/di.xml +++ b/app/code/Magento/Reports/etc/di.xml @@ -29,4 +29,5 @@ <argument name="modulePrefix" xsi:type="string">reports</argument> </arguments> </type> + <preference for="Magento\Reports\Model\Resource\Quote\CollectionFactoryInterface" type="Magento\Reports\Model\Resource\Quote\CollectionFactory"/> </config> diff --git a/app/etc/di.xml b/app/etc/di.xml index 4d5108dd40a54d3ddd48259425e58fbcb17746b3..6ae1eaa1529f5d0f392f41de92ef6248fa7d94f0 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -25,6 +25,7 @@ <preference for="Magento\Framework\View\Design\ThemeInterface" type="Magento\Theme\Model\Theme" /> <preference for="Magento\Framework\View\Design\Theme\ResolverInterface" type="Magento\Theme\Model\Theme\Resolver" /> <preference for="Magento\Framework\View\ConfigInterface" type="Magento\Framework\View\Config" /> + <preference for="Magento\Framework\View\Asset\Bundle\ConfigInterface" type="\Magento\Framework\View\Asset\Bundle\Config" /> <preference for="Magento\Framework\Locale\ListsInterface" type="Magento\Framework\Locale\Lists" /> <preference for="Magento\Framework\Api\AttributeTypeResolverInterface" type="Magento\Framework\Reflection\AttributeTypeResolver" /> <type name="Magento\Store\Model\Store"> @@ -126,7 +127,6 @@ <preference for="Magento\Framework\Pricing\Amount\AmountInterface" type="Magento\Framework\Pricing\Amount\Base" /> <preference for="Magento\Framework\Api\SearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Framework\Api\AttributeInterface" type="Magento\Framework\Api\AttributeValue" /> - <preference for="Magento\Framework\View\Asset\Bundle\ConfigInterface" type="Magento\Framework\View\Asset\Bundle\Config" /> <preference for="Magento\Framework\Model\Resource\Db\TransactionManagerInterface" type="Magento\Framework\Model\Resource\Db\TransactionManager" /> <type name="Magento\Framework\Model\Resource\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Logger\Handler\Base"> diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index c30919ebb1f5c62039e0da4c839ca7edeec1b144..ab0b81f25309992c6d60ad136302bf66d75640e1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -3105,6 +3105,7 @@ return [ ['Magento\Setup\Module\SetupFactory'], ['Magento\Framework\Module\Updater\SetupFactory'], ['Magento\Backend\Model\Config\Source\Yesno', 'Magento\Config\Model\Config\Source\Yesno'], + ['Magento\Reports\Model\Resource\Shopcart\Product\Collection'], ['Zend_Locale', '\Locale, \ResourceBundle'], ['Zend_Locale_Data', '\Locale, \ResourceBundle'], ['Zend_Locale_Content', '\Locale, \ResourceBundle'], 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 b424e9e934459404d0da1f84d47933524671ea3d..50e8d13bcdb0bf2b36a009ea45bef00d894a28c2 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 @@ -2105,6 +2105,8 @@ return [ 'Magento\Integration\Helper\Validator', 'Magento\Integration\Model\CredentialsValidator::validate' ], + ['getProductEntityTypeId', 'Magento\Reports\Model\Resource\Product\Collection'], + ['setProductEntityTypeId', 'Magento\Reports\Model\Resource\Product\Collection'], ['bindLocale', 'Magento\Backend\Model\Observer'], ['getLocaleLists', 'Magento\Dhl\Model\Resource\Setup', 'getLocaleResolver'], ['getTranslationList', 'Magento\Framework\Locale\Lists', '\ResourceBundle'], diff --git a/lib/internal/Magento/Framework/Api/Builder.php b/lib/internal/Magento/Framework/Api/Builder.php index 8096e4722c257993b248cbdba9f83febfe458754..9b7338a54e2788624477f5131966860e36b4b845 100644 --- a/lib/internal/Magento/Framework/Api/Builder.php +++ b/lib/internal/Magento/Framework/Api/Builder.php @@ -265,7 +265,7 @@ class Builder implements BuilderInterface } elseif (is_subclass_of($dataType, '\Magento\Framework\Model\AbstractExtensibleModel')) { return self::TYPE_DATA_MODEL; } - + $dataType = ltrim($dataType, '\\'); $sourceClassPreference = $this->objectManagerConfig->getPreference($dataType); if (empty($sourceClassPreference)) { throw new \LogicException(