From 193d007f087c8048303e6aa8680f4af5fd1aa272 Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 27 Apr 2018 14:36:53 -0500 Subject: [PATCH 01/37] 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 02/37] 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 74778c664f2dad9db52bf64af95f4675708117de Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Tue, 1 May 2018 15:27:08 -0500 Subject: [PATCH 03/37] 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 04/37] 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 4ed0792100f0fb84dc57aac624d4ee26bccf6d81 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 3 May 2018 10:57:46 +0300 Subject: [PATCH 05/37] 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 06/37] 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 276082a0fce9d8e3f0673291fe7900ea55dd2a72 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 3 May 2018 14:54:57 +0300 Subject: [PATCH 07/37] 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 08/37] 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 09/37] 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 10/37] 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 585906bd00961f1c0443a590a385f0d0c158f53f Mon Sep 17 00:00:00 2001 From: Stanislav Lopukhov Date: Fri, 4 May 2018 09:13:15 +0300 Subject: [PATCH 11/37] 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 12/37] 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 13/37] 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 33e4a1dc748b60d3327acc21675fc131a161327b Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Fri, 4 May 2018 10:21:55 -0500 Subject: [PATCH 14/37] 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 435b165e37048955fdefd4c4d9537ac1dc2d260e Mon Sep 17 00:00:00 2001 From: gwharton Date: Sat, 5 May 2018 23:12:05 +0100 Subject: [PATCH 15/37] [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 16/37] [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 17/37] 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 18/37] 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 19/37] 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 20/37] 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 b76ddf8859d32d9db4408bbd363c784582eff08c Mon Sep 17 00:00:00 2001 From: Daniel Ruf Date: Sat, 5 May 2018 20:57:27 +0200 Subject: [PATCH 21/37] fix: set message-success in setup if we already have the latest version --- setup/pub/magento/setup/select-version.js | 2 ++ setup/view/magento/setup/select-version.phtml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup/pub/magento/setup/select-version.js b/setup/pub/magento/setup/select-version.js index d9d4f7518ad1f..32210d29dcbfe 100644 --- a/setup/pub/magento/setup/select-version.js +++ b/setup/pub/magento/setup/select-version.js @@ -13,6 +13,7 @@ angular.module('select-version', ['ngStorage']) $scope.upgradeReadyForNext = false; $scope.upgradeProcessed = false; $scope.upgradeProcessError = false; + $scope.upgradeAlreadyLatestVersion = false; $scope.upgradeProcessErrorMessage = ''; $scope.componentsReadyForNext = true; $scope.componentsProcessed = false; @@ -39,6 +40,7 @@ angular.module('select-version', ['ngStorage']) if ($scope.upgradeProcessError) { $scope.upgradeProcessErrorMessage = "You're already using the latest version, there's nothing for us to do."; + $scope.upgradeAlreadyLatestVersion = true; } else { $scope.selectedOption = []; $scope.versions = []; diff --git a/setup/view/magento/setup/select-version.phtml b/setup/view/magento/setup/select-version.phtml index 4c61837d975a0..c78392bd495da 100644 --- a/setup/view/magento/setup/select-version.phtml +++ b/setup/view/magento/setup/select-version.phtml @@ -38,7 +38,7 @@ Checking for a new version... -
+
From 40d9180164792fcedb5d26b3baae8cb363d6409d Mon Sep 17 00:00:00 2001 From: Daniel Ruf Date: Sat, 5 May 2018 21:31:42 +0200 Subject: [PATCH 22/37] fix: break line into multiple lines to fix PHPCS violation --- setup/view/magento/setup/select-version.phtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup/view/magento/setup/select-version.phtml b/setup/view/magento/setup/select-version.phtml index c78392bd495da..920ab86252514 100644 --- a/setup/view/magento/setup/select-version.phtml +++ b/setup/view/magento/setup/select-version.phtml @@ -38,7 +38,10 @@ Checking for a new version...
-
+
From 9f13c1a14f020584272f9f6b103dcc3034997e9d Mon Sep 17 00:00:00 2001 From: Sviatoslav Mankivskyi Date: Mon, 7 May 2018 14:30:24 -0500 Subject: [PATCH 23/37] DEVOPS-2174: Fix integration tests --- .../Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 8ca2e08ea683b..f362e75ea790e 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -105,8 +105,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 &"); + return exec("{$command} >/dev/null &"); }); } From b6e2ad063fdf1910e2cdd531de637a6faa59a3f1 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Tue, 8 May 2018 08:41:27 +0200 Subject: [PATCH 24/37] Duplicate Order Confirmation Emails for PayPal Express checkout order fix --- app/code/Magento/Paypal/Model/Express/Checkout.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index e9f2c1b8415a8..2559ec733a42d 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -809,7 +809,9 @@ public function place($token, $shippingMethodCode = null) case \Magento\Sales\Model\Order::STATE_PROCESSING: case \Magento\Sales\Model\Order::STATE_COMPLETE: case \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW: - $this->orderSender->send($order); + if (!$order->getEmailSent()) { + $this->orderSender->send($order); + } $this->_checkoutSession->start(); break; default: From e07e762b4897c194b4d823c179becf3a94e218f3 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Tue, 8 May 2018 09:06:23 +0200 Subject: [PATCH 25/37] Add statement to 'beforeSave' method to allow app:config:import --- .../Model/Adminhtml/System/Config/CountryCreditCard.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php index f68b7eca047f5..2a9923a333cef 100644 --- a/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php +++ b/app/code/Magento/Braintree/Model/Adminhtml/System/Config/CountryCreditCard.php @@ -66,6 +66,13 @@ public function __construct( public function beforeSave() { $value = $this->getValue(); + if (!is_array($value)) { + try { + $value = $this->serializer->unserialize($value); + } catch (\InvalidArgumentException $e) { + $value = []; + } + } $result = []; foreach ($value as $data) { if (empty($data['country_id']) || empty($data['cc_types'])) { From 52971ba3f116a1271440c3882b72a78400495132 Mon Sep 17 00:00:00 2001 From: Stanislav Lopukhov Date: Tue, 8 May 2018 09:58:38 +0300 Subject: [PATCH 26/37] MAGETWO-90563: [Forwardport] Implement segmentation for Category Product Indexer --- .../Category/Product/AbstractAction.php | 7 +++-- .../Model/Import/Product/Type/BundleTest.php | 30 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 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 522283a0fbe44..b44a173e1b91d 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php @@ -411,8 +411,11 @@ protected function isRangingNeeded() * @param int $range * @return Select[] */ - protected function prepareSelectsByRange(Select $select, $field, $range = self::RANGE_CATEGORY_STEP) - { + protected function prepareSelectsByRange( + Select $select, + string $field, + int $range = self::RANGE_CATEGORY_STEP + ) { if ($this->isRangingNeeded()) { $iterator = $this->queryGenerator->generate( $field, diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php index 29dd6550e4ece..e65d518af3419 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Import/Product/Type/BundleTest.php @@ -5,14 +5,14 @@ */ namespace Magento\BundleImportExport\Model\Import\Product\Type; -use Magento\Framework\App\Bootstrap; +use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\ImportExport\Model\Import; /** * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class BundleTest extends \PHPUnit\Framework\TestCase +class BundleTest extends \Magento\TestFramework\Indexer\TestCase { /** * Bundle product test Name @@ -41,9 +41,22 @@ class BundleTest extends \PHPUnit\Framework\TestCase */ protected $optionSkuList = ['Simple 1', 'Simple 2', 'Simple 3']; + public static function setUpBeforeClass() + { + $db = Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); $this->model = $this->objectManager->create(\Magento\CatalogImportExport\Model\Import\Product::class); } @@ -122,6 +135,7 @@ public function testBundleImport() /** * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDbIsolation disabled * @magentoAppArea adminhtml * @return void */ @@ -179,4 +193,12 @@ public function testBundleImportWithMultipleStoreViews(): void } } } + + /** + * teardown + */ + public function tearDown() + { + parent::tearDown(); + } } From 6771f36b410855656c6a4df206b59fdda80972f9 Mon Sep 17 00:00:00 2001 From: Thomas Klein Date: Mon, 9 Apr 2018 21:42:50 +0200 Subject: [PATCH 27/37] Code cleanup, add more visibility --- .../Model/Condition/AbstractCondition.php | 98 +++++++------------ 1 file changed, 34 insertions(+), 64 deletions(-) diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index 01e715f06a27f..e41620c953412 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -24,7 +24,6 @@ abstract class AbstractCondition extends \Magento\Framework\DataObject implement { /** * Defines which operators will be available for this condition - * * @var string */ protected $_inputType = null; @@ -84,17 +83,11 @@ public function __construct(Context $context, array $data = []) $options = $this->getAttributeOptions(); if ($options) { - foreach (array_keys($options) as $attr) { - $this->setAttribute($attr); - break; - } + $this->setAttribute(key($options)); } $options = $this->getOperatorOptions(); if ($options) { - foreach (array_keys($options) as $operator) { - $this->setOperator($operator); - break; - } + $this->setOperator(key($options)); } } @@ -160,14 +153,13 @@ public function getForm() */ public function asArray(array $arrAttributes = []) { - $out = [ + return [ 'type' => $this->getType(), 'attribute' => $this->getAttribute(), 'operator' => $this->getOperator(), 'value' => $this->getValue(), 'is_value_processed' => $this->getIsValueParsed(), ]; - return $out; } /** @@ -205,7 +197,7 @@ public function getMappedSqlField() */ public function asXml() { - $xml = "" . + return "" . $this->getType() . "" . "" . @@ -217,7 +209,6 @@ public function asXml() "" . $this->getValue() . ""; - return $xml; } /** @@ -244,8 +235,7 @@ public function loadXml($xml) if (is_string($xml)) { $xml = simplexml_load_string($xml); } - $arr = (array)$xml; - $this->loadArray($arr); + $this->loadArray((array)$xml); return $this; } @@ -304,10 +294,7 @@ public function loadOperatorOptions() */ public function getInputType() { - if (null === $this->_inputType) { - return 'string'; - } - return $this->_inputType; + return null === $this->_inputType ? 'string' : $this->_inputType; } /** @@ -348,12 +335,11 @@ public function loadValueOptions() */ public function getValueSelectOptions() { - $valueOption = $opt = []; + $opt = []; if ($this->hasValueOption()) { - $valueOption = (array)$this->getValueOption(); - } - foreach ($valueOption as $key => $value) { - $opt[] = ['value' => $key, 'label' => $value]; + foreach ((array)$this->getValueOption() as $key => $value) { + $opt[] = ['value' => $key, 'label' => $value]; + } } return $opt; } @@ -470,13 +456,12 @@ public function getNewChildName() */ public function asHtml() { - $html = $this->getTypeElementHtml() . + return $this->getTypeElementHtml() . $this->getAttributeElementHtml() . $this->getOperatorElementHtml() . $this->getValueElementHtml() . $this->getRemoveLinkHtml() . $this->getChooserContainerHtml(); - return $html; } /** @@ -484,8 +469,7 @@ public function asHtml() */ public function asHtmlRecursive() { - $html = $this->asHtml(); - return $html; + return $this->asHtml(); } /** @@ -520,9 +504,9 @@ public function getTypeElementHtml() public function getAttributeElement() { if (null === $this->getAttribute()) { - foreach (array_keys($this->getAttributeOption()) as $option) { - $this->setAttribute($option); - break; + $options = $this->getAttributeOptions(); + if ($options) { + $this->setAttribute(key($options)); } } return $this->getForm()->addField( @@ -558,10 +542,8 @@ public function getOperatorElement() { $options = $this->getOperatorSelectOptions(); if ($this->getOperator() === null) { - foreach ($options as $option) { - $this->setOperator($option['value']); - break; - } + $option = current($options); + $this->setOperator($option['value']); } $elementId = sprintf('%s__%s__operator', $this->getPrefix(), $this->getId()); @@ -654,8 +636,7 @@ public function getValueElementHtml() public function getAddLinkHtml() { $src = $this->_assetRepo->getUrl('images/rule_component_add.gif'); - $html = ''; - return $html; + return ''; } /** @@ -676,11 +657,7 @@ public function getRemoveLinkHtml() public function getChooserContainerHtml() { $url = $this->getValueElementChooserUrl(); - $html = ''; - if ($url) { - $html = '
'; - } - return $html; + return $url ? '
' : ''; } /** @@ -690,8 +667,7 @@ public function getChooserContainerHtml() */ public function asString($format = '') { - $str = $this->getAttributeName() . ' ' . $this->getOperatorName() . ' ' . $this->getValueName(); - return $str; + return $this->getAttributeName() . ' ' . $this->getOperatorName() . ' ' . $this->getValueName(); } /** @@ -700,8 +676,7 @@ public function asString($format = '') */ public function asStringRecursive($level = 0) { - $str = str_pad('', $level * 3, ' ', STR_PAD_LEFT) . $this->asString(); - return $str; + return str_pad('', $level * 3, ' ', STR_PAD_LEFT) . $this->asString(); } /** @@ -740,12 +715,10 @@ public function validateAttribute($validatedValue) case '==': case '!=': if (is_array($value)) { - if (is_array($validatedValue)) { - $result = array_intersect($value, $validatedValue); - $result = !empty($result); - } else { + if (!is_array($validatedValue)) { return false; } + $result = !empty(array_intersect($value, $validatedValue)); } else { if (is_array($validatedValue)) { $result = count($validatedValue) == 1 && array_shift($validatedValue) == $value; @@ -759,18 +732,16 @@ public function validateAttribute($validatedValue) case '>': if (!is_scalar($validatedValue)) { return false; - } else { - $result = $validatedValue <= $value; } + $result = $validatedValue <= $value; break; case '>=': case '<': if (!is_scalar($validatedValue)) { return false; - } else { - $result = $validatedValue >= $value; } + $result = $validatedValue >= $value; break; case '{}': @@ -783,12 +754,11 @@ public function validateAttribute($validatedValue) } } } elseif (is_array($value)) { - if (is_array($validatedValue)) { - $result = array_intersect($value, $validatedValue); - $result = !empty($result); - } else { + if (!is_array($validatedValue)) { return false; } + $result = array_intersect($value, $validatedValue); + $result = !empty($result); } else { if (is_array($validatedValue)) { $result = in_array($value, $validatedValue); @@ -833,13 +803,13 @@ protected function _compareValues($validatedValue, $value, $strict = true) { if ($strict && is_numeric($validatedValue) && is_numeric($value)) { return $validatedValue == $value; - } else { - $validatePattern = preg_quote($validatedValue, '~'); - if ($strict) { - $validatePattern = '^' . $validatePattern . '$'; - } - return (bool)preg_match('~' . $validatePattern . '~iu', $value); } + + $validatePattern = preg_quote($validatedValue, '~'); + if ($strict) { + $validatePattern = '^' . $validatePattern . '$'; + } + return (bool)preg_match('~' . $validatePattern . '~iu', $value); } /** From 79b74b60aea0b66d7acda98e960d164322d36784 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Sat, 28 Apr 2018 11:35:01 +0300 Subject: [PATCH 28/37] Fixed typo and logic mismatch --- .../Magento/Rule/Model/Condition/AbstractCondition.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index e41620c953412..192b0e40d9983 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -83,11 +83,11 @@ public function __construct(Context $context, array $data = []) $options = $this->getAttributeOptions(); if ($options) { - $this->setAttribute(key($options)); + $this->setAttribute(key(reset($options))); } $options = $this->getOperatorOptions(); if ($options) { - $this->setOperator(key($options)); + $this->setOperator(key(reset($options))); } } @@ -504,9 +504,9 @@ public function getTypeElementHtml() public function getAttributeElement() { if (null === $this->getAttribute()) { - $options = $this->getAttributeOptions(); + $options = $this->getAttributeOption(); if ($options) { - $this->setAttribute(key($options)); + $this->setAttribute(key(reset($options))); } } return $this->getForm()->addField( @@ -542,7 +542,7 @@ public function getOperatorElement() { $options = $this->getOperatorSelectOptions(); if ($this->getOperator() === null) { - $option = current($options); + $option = current(reset($options)); $this->setOperator($option['value']); } From 4276a24a774a72a1618d93fda265cadf1a6fccc4 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov Date: Sat, 28 Apr 2018 15:57:11 +0300 Subject: [PATCH 29/37] Fixed reset method usage --- .../Rule/Model/Condition/AbstractCondition.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index 192b0e40d9983..e3bec2d9959b1 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -83,11 +83,13 @@ public function __construct(Context $context, array $data = []) $options = $this->getAttributeOptions(); if ($options) { - $this->setAttribute(key(reset($options))); + reset($options); + $this->setAttribute(key($options)); } $options = $this->getOperatorOptions(); if ($options) { - $this->setOperator(key(reset($options))); + reset($options); + $this->setOperator(key($options)); } } @@ -506,7 +508,8 @@ public function getAttributeElement() if (null === $this->getAttribute()) { $options = $this->getAttributeOption(); if ($options) { - $this->setAttribute(key(reset($options))); + reset($options); + $this->setAttribute(key($options)); } } return $this->getForm()->addField( @@ -542,7 +545,7 @@ public function getOperatorElement() { $options = $this->getOperatorSelectOptions(); if ($this->getOperator() === null) { - $option = current(reset($options)); + $option = reset($options); $this->setOperator($option['value']); } From a132f549d683cc9053f91ab80935b16bc27237d5 Mon Sep 17 00:00:00 2001 From: Sean Templeton Date: Thu, 26 Apr 2018 13:01:36 -0500 Subject: [PATCH 30/37] Changed return type of addToCartPostParams to array \Magento\Catalog\Block\Product\ListProduct::getAddToCartPostParams incorrectly defined a string return type when in fact it's an array --- app/code/Magento/Catalog/Block/Product/ListProduct.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index ee63d3400ade5..c1b255c762dbb 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -356,7 +356,7 @@ public function getIdentities() * Get post parameters * * @param Product $product - * @return string + * @return array */ public function getAddToCartPostParams(Product $product) { From 361d576e60cbcb19c6f200774bfb8dab55a7bf2d Mon Sep 17 00:00:00 2001 From: Yogesh Suhagiya Date: Sat, 28 Apr 2018 18:36:02 +0530 Subject: [PATCH 31/37] Removed extra spaces from language file --- app/code/Magento/CatalogInventory/i18n/en_US.csv | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/code/Magento/CatalogInventory/i18n/en_US.csv b/app/code/Magento/CatalogInventory/i18n/en_US.csv index 93406163cbe1b..19b73f847b46d 100644 --- a/app/code/Magento/CatalogInventory/i18n/en_US.csv +++ b/app/code/Magento/CatalogInventory/i18n/en_US.csv @@ -55,11 +55,7 @@ Inventory,Inventory "Only X left Threshold","Only X left Threshold" "Display Products Availability in Stock on Storefront","Display Products Availability in Stock on Storefront" "Product Stock Options","Product Stock Options" -" - Please note that these settings apply to individual items in the cart, not to the entire cart. - "," - Please note that these settings apply to individual items in the cart, not to the entire cart. - " +"Please note that these settings apply to individual items in the cart, not to the entire cart.","Please note that these settings apply to individual items in the cart, not to the entire cart." "Manage Stock","Manage Stock" Backorders,Backorders "Maximum Qty Allowed in Shopping Cart","Maximum Qty Allowed in Shopping Cart" From 435650b2a15e042380aa8d4001f06108df9b5a36 Mon Sep 17 00:00:00 2001 From: Yogesh Suhagiya Date: Wed, 2 May 2018 13:08:17 +0530 Subject: [PATCH 32/37] Removed extra close tag --- app/code/Magento/Review/view/frontend/templates/view.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Review/view/frontend/templates/view.phtml b/app/code/Magento/Review/view/frontend/templates/view.phtml index 564a6e1a7c537..205ce5c20572a 100644 --- a/app/code/Magento/Review/view/frontend/templates/view.phtml +++ b/app/code/Magento/Review/view/frontend/templates/view.phtml @@ -49,7 +49,7 @@
- escapeHtml(__('Back to Product Reviews')) ?> + escapeHtml(__('Back to Product Reviews')) ?>
From 44a1f2bd5124a1cf18e6b44505744977ee05b7df Mon Sep 17 00:00:00 2001 From: jakhotiya Date: Thu, 3 May 2018 00:23:30 +0530 Subject: [PATCH 33/37] use Module_Name/template/path format instead of using template/path in Block class to ensure extensibility of the class. not doing so causes invalid template errors after extending the block class via preference --- app/code/Magento/Review/Block/Product/ReviewRenderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Review/Block/Product/ReviewRenderer.php b/app/code/Magento/Review/Block/Product/ReviewRenderer.php index 8aa10d2437cbb..3cd15aba30420 100644 --- a/app/code/Magento/Review/Block/Product/ReviewRenderer.php +++ b/app/code/Magento/Review/Block/Product/ReviewRenderer.php @@ -18,8 +18,8 @@ class ReviewRenderer extends \Magento\Framework\View\Element\Template implements * @var array */ protected $_availableTemplates = [ - self::FULL_VIEW => 'helper/summary.phtml', - self::SHORT_VIEW => 'helper/summary_short.phtml', + self::FULL_VIEW => 'Magento_Review::helper/summary.phtml', + self::SHORT_VIEW => 'Magento_Review::helper/summary_short.phtml', ]; /** From c68b1af196c31f846dae939fa336acd9c91d114e Mon Sep 17 00:00:00 2001 From: Kalpesh Mehta Date: Sat, 5 May 2018 15:29:38 -0700 Subject: [PATCH 34/37] Fixed typo in _buttons.less --- lib/web/css/docs/source/_buttons.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web/css/docs/source/_buttons.less b/lib/web/css/docs/source/_buttons.less index a81599c517ff8..a071eed85ef9e 100644 --- a/lib/web/css/docs/source/_buttons.less +++ b/lib/web/css/docs/source/_buttons.less @@ -397,7 +397,7 @@ button { // # Button with an icon on the left or right side of the text // -// @_button-icon-font-position variable is used to set up the icon postiton +// @_button-icon-font-position variable is used to set up the icon position // // Use before to set up button icon position on the left: // ```css @@ -818,7 +818,7 @@ button { // # Link as a button // -// The .lib-link-as-button() mixin is used to reset link styles (text-decoration and display inline). Then to make it look like a button, you need to add the .lib-button() mixin with appropriate paremeters. +// The .lib-link-as-button() mixin is used to reset link styles (text-decoration and display inline). Then to make it look like a button, you need to add the .lib-button() mixin with appropriate parameters. // // ```html // Button From f48135bf1b857ca69ddaba69fb8e5af0755cfab0 Mon Sep 17 00:00:00 2001 From: Kalpesh Mehta Date: Sat, 5 May 2018 15:30:48 -0700 Subject: [PATCH 35/37] Fixed typo in _typography.less --- lib/web/css/docs/source/_typography.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web/css/docs/source/_typography.less b/lib/web/css/docs/source/_typography.less index 294f6446fdf13..b360c672a6d7c 100644 --- a/lib/web/css/docs/source/_typography.less +++ b/lib/web/css/docs/source/_typography.less @@ -3,7 +3,7 @@ // * See COPYING.txt for license details. // */ -// # Typogrphy +// # Typography // // Magento UI library provides mixins for typography styling. To configure typography, global variables from **_variables.less** file are used. // @@ -978,7 +978,7 @@ // # Text hide // -// The .lib-text-hide() mixin hides a text of the element the mixin is applyed to. +// The .lib-text-hide() mixin hides a text of the element the mixin is applied to. // // ```html //

From 32bd463597c554a7825b33c4e3f60273895a9466 Mon Sep 17 00:00:00 2001 From: Kalpesh Mehta Date: Sat, 5 May 2018 15:31:34 -0700 Subject: [PATCH 36/37] Fixed typo in _layout.less --- lib/web/css/docs/source/_layout.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web/css/docs/source/_layout.less b/lib/web/css/docs/source/_layout.less index 37598197142f6..799f940b44c02 100644 --- a/lib/web/css/docs/source/_layout.less +++ b/lib/web/css/docs/source/_layout.less @@ -91,7 +91,7 @@ footer.footer { // @gutter-width // 0 // '' | false | value -// Distanse between columns +// Distance between columns // // // Variables for layout columns @@ -361,7 +361,7 @@ footer.footer { // # Layout width // -// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applyed to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. +// The .lib-layout-width() mixin is used to set default page width of the element the mixin is applied to. It can be used to set width for the whole page wrapper or for the page elements individualy like header, footer, and so on. // # Layout width variables // From 435d241cbf5552cbae2dc6029e9442cc4e1d5f9f Mon Sep 17 00:00:00 2001 From: Daniel Ruf Date: Sat, 5 May 2018 22:11:25 +0200 Subject: [PATCH 37/37] chore: use random_int() in some places --- .../Backend/Block/Widget/Grid/Column/Renderer/Massaction.php | 2 +- .../Catalog/Model/Indexer/Category/Product/AbstractAction.php | 2 +- app/code/Magento/SalesRule/Model/Coupon/Codegenerator.php | 4 ++-- app/code/Magento/SalesRule/Model/Rule.php | 2 +- lib/internal/Magento/Framework/Encryption/Crypt.php | 2 +- .../Magento/Framework/Setup/BackendFrontnameGenerator.php | 2 +- lib/internal/Magento/Framework/Webapi/ErrorProcessor.php | 2 +- pub/errors/processor.php | 2 +- .../src/Magento/Setup/Model/Address/AddressDataGenerator.php | 2 +- setup/src/Magento/Setup/Model/DataGenerator.php | 4 ++-- .../Magento/Setup/Model/Description/DescriptionGenerator.php | 2 +- .../Setup/Model/Description/DescriptionParagraphGenerator.php | 2 +- .../Setup/Model/Description/DescriptionSentenceGenerator.php | 2 +- setup/src/Magento/Setup/Model/Description/Mixin/BoldMixin.php | 2 +- .../Model/Description/Mixin/Helper/RandomWordSelector.php | 2 +- .../src/Magento/Setup/Model/Description/Mixin/ItalicMixin.php | 2 +- setup/src/Magento/Setup/Model/Description/Mixin/SpanMixin.php | 2 +- setup/src/Magento/Setup/Model/Dictionary.php | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php index 320713f8b57c4..a611e91f32f00 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/Massaction.php @@ -65,7 +65,7 @@ public function render(\Magento\Framework\DataObject $row) */ protected function _getCheckboxHtml($value, $checked) { - $id = 'id_' . rand(0, 999); + $id = 'id_' . random_int(0, 999); $html = '